Merge pull request #239 from nikhilbadyal/230-custom-patch-resources

230 custom patch resources
This commit is contained in:
Nikhil Badyal
2023-08-06 08:47:20 -07:00
committed by GitHub
12 changed files with 402 additions and 447 deletions
+1 -1
View File
@@ -7,4 +7,4 @@ venv
/revanced-cache/
changelog.md
.idea
*patches.json
*.json
+150 -127
View File
@@ -1,15 +1,11 @@
# 🤓Docker-Py-ReVanced
A little python script that will help you in building Revanced and Revanced-Extended [apps](#note).
A little python script that will help you in building Revanced [apps](#patch-apps).
**`Note`** - If you are a root user and want magisk module (Extended). Get them [here](https://github.com/nikhilbadyal/revanced-magisk-module)
This is just a builder for revanced and not a revanced support. Please be understanding and refrain from asking
about revanced features/bugs. Discuss those on proper relevant forums(on Revanced GitHub , Discord)
**`Note`** - I prefer [Revanced Extended](https://github.com/inotia00/revanced-patches/tree/revanced-extended) more
(for YouTube & YouTube Music) hence the YouTube and YouTube Music builds in this repo are from
Revanced Extended.
about revanced features/bugs. Discuss those on proper relevant forums.
## Pre-Built APKs
@@ -19,7 +15,7 @@ You can get pre-built apks [here](https://revanced_apkss.t.me/)
You can use any of the following methods to build.
- 🚀In GitHub (**_`Recommended`_**)
- 🚀 **_GitHub**_ (**_`Recommended`_**)
1. Click Star to support the project.<br>
<img src="https://i.imgur.com/FFyXaWY.png" width="400" style="left"><br>
@@ -51,7 +47,7 @@ You can use any of the following methods to build.
5. If the building process is successful, youll get your APKs in the<br>
<img src="https://i.imgur.com/S5d7qAO.png" width="700" style="left">
- 🐳With Docker Compose
- 🐳 **_Docker Compose_**<br>
Windows/Mac users simply install Docker Desktop. If using Linux see below
1. Install Docker(Skip if already installed)
@@ -80,7 +76,7 @@ You can use any of the following methods to build.
6. Update `.env` file if you want some customization(See notes)
7. Run script with
```shell
docker-compose up
docker-compose up --build
```
- 🐳With Docker
@@ -99,7 +95,7 @@ You can use any of the following methods to build.
- 🫠Without Docker
1. Install Java17 (zulu preferred)
1. Install Java >= 17
2. Install Python
3. Create virtual environment
```
@@ -115,15 +111,53 @@ You can use any of the following methods to build.
```
6. Run the script with
```
python python main.py
python main.py
```
## Configurations
### Global Config
| **Env Name** | **Description** | **Default** |
|:---------------------------------------------------------|:-------------------------------------------------:|:---------------------------------------------------------------------------------------------------------|
| [PATCH_APPS](#patch-apps) | Apps to patch/build | youtube |
| [EXISTING_DOWNLOADED_APKS ](#existing-downloaded-apks) | Already downloaded clean apks | [] |
| [PERSONAL_ACCESS_TOKEN](#personal-access-token) | Github Token to be used | None |
| DRY_RUN | Do a dry run | False |
| [GLOBAL_CLI_DL*](#global-resources) | DL for CLI to be used for patching apps. | [Revanced CLI](https://github.com/revanced/revanced-cli) |
| [GLOBAL_PATCHES_DL*](#global-resources) | DL for Patches to be used for patching apps. | [Revanced Patches](https://github.com/revanced/revanced-patches) |
| [GLOBAL_PATCHES_JSON_DL*](#global-resources) | DL for Patches Json to be used for patching apps. | [Revanced Patches](https://github.com/revanced/revanced-patches) |
| [GLOBAL_INTEGRATIONS_DL*](#global-resources) | DL for Integrations to be used for patching apps. | [Revanced CLI](https://github.com/revanced/revanced-integrations) |
| [GLOBAL_KEYSTORE_FILE_NAME*](#global-keystore-file-name) | Key file to be used for signing apps | [Builder's own key](https://github.com/nikhilbadyal/docker-py-revanced/blob/main/apks/revanced.keystore) |
| [GLOBAL_ARCHS_TO_BUILD*](#global-archs-to-build) | Arch to keep in the patched apk. | All |
| REDDIT_CLIENT_ID | Reddit Client ID to patch reddit apps | None |
| VT_API_KEY | Virus Total Key to scan APKs | None |
| [TELEGRAM_CHAT_ID](#telegram-support) | Receiver in Telegram upload | None |
| [TELEGRAM_BOT_TOKEN](#telegram-support) | APKs Sender for Telegram upload | None |
| [TELEGRAM_API_ID](#telegram-support) | Used for telegram Authentication | None |
| [TELEGRAM_API_HASH](#telegram-support) | Used for telegram Authentication | None |
`*` - 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**. | GLOBAL_CLI_DL |
| [*APP_NAME*_PATCHES_DL](#global-resources) | DL for Patches to be used for patching **APP_NAME**. | GLOBAL_PATCHES_DL |
| [*APP_NAME*_PATCHES_JSON_DL](#global-resources) | DL for Patches Json to be used for patching **APP_NAME**. | GLOBAL_PATCHES_JSON_DL |
| [*APP_NAME*_INTEGRATIONS_DL](#global-resources) | DL for Integrations to be used for patching **APP_NAME**. | GLOBAL_INTEGRATIONS_DL |
| [*APP_NAME*_KEYSTORE_FILE_NAME](#global-keystore-file-name) | Key file to be used for signing **APP_NAME**. | GLOBAL_KEYSTORE_FILE_NAME |
| [*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 |
`**` - By default all patches for a given app are included.<br>
`**` - Can be used to included universal patch.
## Note
(Pay attention to 3,4)<br>
By default, script build the version as recommended by Revanced team.
1. Supported values for **REVANCED_APPS_NAME** are :
1. Supported values for **APP_NAME** are :
1. [youtube](https://www.apkmirror.com/apk/google-inc/youtube/)
2. [youtube_music](https://www.apkmirror.com/apk/google-inc/youtube-music/)
@@ -165,105 +199,126 @@ By default, script build the version as recommended by Revanced team.
38. [bacon](https://www.apkmirror.com/apk/onelouder-apps/baconreader-for-reddit/)
39. [microg](https://github.com/inotia00/mMicroG/releases)
<br>Please verify the source of original APKs yourself with links provided. I'm not responsible for any damaged caused.
If you know any better/safe source to download clean. Please raise a PR.
2. Remember to download the **_Microg_**. Otherwise, you will not be able to open YouTube.
3. By default, it will build only `youtube`. To build other apps supported by revanced team.
Add the apps you want to build in `.env` file or in `ENVS` in
`GitHub secrets` in the format
<br>Please verify the source of original APKs yourself with links provided. I'm not responsible for any damage
caused.If you know any better/safe source to download clean. Open a discussion.
2. By default, script build the latest version as recommended by `patches.json` team.
3. Remember to download the **_Microg_**. Otherwise, you may not be able to open YouTube/YouTube Music.
4. <a id="patch-apps"></a>By default, tool will build only `youtube`. To build other apps supported by patching
resources.Add the apps you want to build in `.env` file or in `ENVS` in `GitHub secrets` in the format
```ini
PATCH_APPS=<REVANCED_APPS_NAME>
PATCH_APPS=<APP_NAME>
```
Example:
```ini
PATCH_APPS=youtube,twitter,reddit
```
4. If you want to exclude any patch. Set comma separated patch in `.env` file or in `ENVS` in `GitHub secrets`
(Recommended) in the format
5. <a id="existing-downloaded-apks"></a>If APKMirror or other apk sources are blocked in your region or script
somehow is unable to download from apkmirror. You can download apk manually from any source. Place them in
`/apks` directory and provide environment variable in `.env` file or in `ENVS` in `GitHub secrets`(Recommended)
in the format.
```dotenv
EXISTING_DOWNLOADED_APKS=<Comma,Seperate,App,Name>
```
Example:
```dotenv
EXISTING_DOWNLOADED_APKS=youtube,youtube_music
```
If you add above. Script will not download the `youtube` & `youtube_music`apk from internet and expects an apk in
`/apks` folder with **same** name.
6. <a id="personal-access-token"></a>If you run script again & again. You might hit GitHub API limit. In that case
you can provide your Personal GitHub Access Token in `.env` file or in `ENVS` in `GitHub secrets` (Recommended)
in the format -
```dotenv
PERSONAL_ACCESS_TOKEN=<PAT>
```
7. <a id="global-resources"></a>You can provide Direct download to the resource to used for patching apps `.env` file
or in `ENVS` in `GitHub secrets` (Recommended) in the format -
```dotenv
GLOBAL_CLI_DL=https://github.com/revanced/revanced-cli
GLOBAL_PATCHES_DL=https://github.com/revanced/revanced-patches
GLOBAL_PATCHES_JSON_DL=https://github.com/revanced/revanced-patches
GLOBAL_INTEGRATIONS_DL=https://github.com/revanced/revanced-integrations
```
Resources downloaded from envs and will be used for patching for any **APP_NAME**.
Unless provided different resource for the individual app.<br><br>
Tool also support resource config at app level. You can patch A app with X resources while patching B with Y
resources.
This can be done by providing Direct download link for resources for app.<br>
Example:
```dotenv
YOUTUBE_CLI_DL=https://github.com/inotia00/revanced-cli
YOUTUBE_PATCHES_DL=https://github.com/inotia00/revanced-patches
YOUTUBE_PATCHES_JSON_DL=https://github.com/inotia00/revanced-patches
YOUTUBE_INTEGRATIONS_DL=https://github.com/inotia00/revanced-integrations
```
With the config tool will try to patch youtube with resources from inotia00 while other global resource will used
for patching other apps.
*Note* - The link provided must be DLs. Unless they are from GitHub.
8. <a id="global-keystore-file-name"></a>If you don't want to use default keystore. You can provide your own by
placing it inside `apks` folder. And adding the name of `keystore-file` in `.env` file or in `ENVS` in `GitHub
secrets` (Recommended) in the format
```dotenv
GLOBAL_KEYSTORE_FILE_NAME=revanced.keystore
```
Tool also support providing secret key at app level. You can sign A app with X key while signing B with Y
key.<br>
Example:
```dotenv
YOUTUBE_KEYSTORE_FILE_NAME=youtube.keystore
```
9. <a id="global-archs-to-build"></a>You can build only for a particular arch in order to get smaller apk files.This
can be done with by adding comma separated `ARCHS_TO_BUILD` in `ENVS` in `GitHub secrets` (Recommended) in the
format.
```dotenv
GLOABAL_ARCHS_TO_BUILD=arm64-v8a,armeabi-v7a
```
Tool also support configuring at app level.<br>
Example:
```dotenv
YOUTUBE_ARCHS_TO_BUILD=arm64-v8a,armeabi-v7a
```
*Note* -
1. Possible values are: `armeabi-v7a`,`x86`,`x86_64`,`arm64-v8a`
2. Make sure the patching resource(CLI) support this feature.
10. <a id="custom-exclude-patching"></a>If you want to exclude any patch. Set comma separated patch in `.env` file
or in `ENVS` in `GitHub secrets` (Recommended) in the format
```ini
EXCLUDE_PATCH_<REVANCED_APPS_NAME>=<PATCH_TO_EXCLUDE-1,PATCH_TO_EXCLUDE-2>
<APP_NAME>_EXCLUDE_PATCH=<PATCH_TO_EXCLUDE-1,PATCH_TO_EXCLUDE-2>
```
Example:
```dotenv
EXCLUDE_PATCH_YOUTUBE=custom-branding,hide-get-premium
EXCLUDE_PATCH_YOUTUBE_MUSIC=yt-music-is-shit
YOUTUBE_EXCLUDE_PATCH=custom-branding,hide-get-premium
YOUTUBE_MUSIC_EXCLUDE_PATCH=yt-music-is-shit
```
If you are using `Revanced Extended.` Add `_EXTENDED` in exclude options.
Example:
```dotenv
EXCLUDE_PATCH_YOUTUBE_EXTENDED=custom-branding-red,custom-branding-blue,materialyou
EXCLUDE_PATCH_YOUTUBE_MUSIC_EXTENDED=custom-branding-music
```
**_All the patches for an app are included by default._**.<br><br>If you want to apply a universal patch. You can
include it
manually. See below for more information.<br>
If you want to include any universal patch. Set comma separated patch in `.env` file or in `ENVS` in `GitHub
secrets`
(Recommended) in the format
Note -
1. **All** the patches for an app are **included** by default.<br>
2. Revanced patches are provided as space separated, make sure you type those **-** separated here.
It means a patch named _**Hey There**_ must be entered as **_hey-there_** in the above example.
11. <a id="custom-include-patching"></a>If you want to include any universal patch. Set comma separated patch in `.env`
file or in `ENVS` in `GitHub secrets` (Recommended) in the format
```ini
INCLUDE_PATCH_<REVANCED_APPS_NAME>=<PATCH_TO_EXCLUDE-1,PATCH_TO_EXCLUDE-2>
<APP_NAME>_INCLUDE_PATCH=<PATCH_TO_EXCLUDE-1,PATCH_TO_EXCLUDE-2>
```
Example:
```dotenv
INCLUDE_PATCH_YOUTUBE=remove-screenshot-restriction
YOUTUBE_INCLUDE_PATCH=remove-screenshot-restriction
```
If you are using `Revanced Extended.` Add `_EXTENDED` in exclude options.
Example:
```dotenv
INCLUDE_PATCH_YOUTUBE_EXTENDED=remove-screenshot-restriction
```
**_Remember_** - Revanced patches are provided space separated, make sure you type those **-** separated here. It means a
patch named _**Hey There**_ will be entered as **_hey-there_** in the above example.
5. If you want to build a specific version . Add `version` in `.env` file or in `ENVS` in `GitHub secrets` (Recommended)
in the format
Note -
1. Revanced patches are provided as space separated, make sure you type those **-** separated here.
It means a patch named _**Hey There**_ must be entered as **_hey-there_** in the above example.
12. <a id="app-version"></a>If you want to build a specific version or latest version. Add `version` in `.env` file
or in `ENVS` in `GitHub secrets` (Recommended) in the format
```ini
<APPNAME>_VERSION=<VERSION>
<APP_NAME>_VERSION=<VERSION>
```
Example:
```ini
YOUTUBE_VERSION=17.31.36
YOUTUBE_MUSIC_VERSION=X.X.X
TWITTER_VERSION=X.X.X
REDDIT_VERSION=X.X.X
TIKTOK_VERSION=X.X.X
WARNWETTER_VERSION=X.X.X
```
6. If you want to build `latest` version, whatever latest is available(including
beta) .
Add `latest` in `.env` file or in `ENVS` in `GitHub secrets` (Recommended) in the format
```ini
<APPNAME>_VERSION=latest
```
Example:
```ini
YOUTUBE_VERSION=latest
YOUTUBE_MUSIC_VERSION=latest
TWITTER_VERSION=latest
REDDIT_VERSION=latest
TIKTOK_VERSION=latest
WARNWETTER_VERSION=latest
```
7. If you don't want to use default keystore. You can provide your own by placing it
inside `apks` folder. And adding the name of `keystore-file` in `.env` file or in `ENVS` in `GitHub secrets`
(Recommended) in the format
```dotenv
KEYSTORE_FILE_NAME=revanced.keystore
```
8. If you want to use Revanced-Extended for YouTube and YouTube Music. Add the following adding
in `.env` file or in `ENVS` in `GitHub secrets` (Recommended) in the format
```dotenv
BUILD_EXTENDED=True
```
or disable it with (default)
```dotenv
BUILD_EXTENDED=False
```
9. For Telegram Upload.
13. <a id="telegram-support"></a>For Telegram Upload.
1. Set up a telegram channel, send a message to it and forward the message to
this telegram [bot](https://t.me/username_to_id_bot)
2. Copy `id` and save it to `TELEGRAM_CHAT_ID`<br>
@@ -275,46 +330,14 @@ By default, script build the version as recommended by Revanced team.
<img src="https://i.imgur.com/eha3nnb.png" width="300" style="left"><br>
5. `TELEGRAM_API_HASH` - Telegram API_HASH is provided by telegram [here](https://my.telegram.org/apps)<br>
<img src="https://i.imgur.com/7n5k1mp.png" width="300" style="left"><br>
6. After Everything done successfully the actions secrets of the repository will look something like<br>
<img src="https://i.imgur.com/dzC1KFa.png" width="400">
10. You can build only for a particular arch in order to get smaller apk files.This can be done with by adding comma
separated `ARCHS_TO_BUILD` in `ENVS` in `GitHub secrets` (Recommended) in the format.
```dotenv
ARCHS_TO_BUILD=arm64-v8a,armeabi-v7a
```
Possible values for `ARCHS_TO_BUILD` are: `armeabi-v7a`,`x86`,`x86_64`,`arm64-v8a`
Make sure you are using `revanced-extended` as `revanced` doesn't support this.
11. You can scan your built apks files with VirusTotal. For that, Add `VT_API_KEY` in `GitHub secrets`.
12. Configuration defined in `ENVS` in `GitHub secrets` will override the configuration in `.env` file. You can use this
6. After Everything done successfully a part of the actions secrets of the repository may look like<br>
<img src="https://i.imgur.com/Cjifz1M.png" width="400">
14. Configuration defined in `ENVS` in `GitHub secrets` will override the configuration in `.env` file. You can use this
fact to define your normal configurations in `.env` file and sometimes if you want to build something different just
once. Add it in `GitHub secrets`.<br>
Or you can ignore what I said above and always use `GitHub secrets`.
13. If APKMirror or other apk source is blocked in your region or script somehow is unable to download from apkmirror.
You can download apk manually from any source. Place them in `/apks` directory and provide environment variable
in `.env` file or in `ENVS` in `GitHub secrets`(Recommended) in the format.
```dotenv
EXISTING_DOWNLOADED_APKS=<Comma,Seperate,App,Name>
```
Example:
```dotenv
EXISTING_DOWNLOADED_APKS=youtube,youtube_music
```
If you add above. Script will not download the `Youtube` & `youtube music`apk from internet and expects an apk in
`/apks` folder.
Name of the downloaded apk must match with the available app choices found [here.](#note)
14. If you run script again & again. You might hit GitHub API limit. In that case you can provide your Personal
GitHub Access Token in `.env` file or in `ENVS` in `GitHub secrets` (Recommended) in the format -
```dotenv
PERSONAL_ACCESS_TOKEN=<PAT>
```
15. Sample Envs<br>
<img src="https://i.imgur.com/ajSE5nA.png" width="600" style="left">
16. Make your Action has write access. If not click
<img src="https://i.imgur.com/FxOtiGs.png" width="600" style="left">
16. Make sure your Action has write access. If not click
[here](https://github.com/nikhilbadyal/docker-py-revanced/settings/actions).
In the bottom give read and write access to Actions.
<img src="https://i.imgur.com/STSv2D3.png" width="400">
17. If you want to patch reddit apps using your own Client ID. You can provide your Client ID
as secret `REDDIT_CLIENT_ID` in `GitHub secrets`.
Thanks to [@aliharslan0](https://github.com/aliharslan0/pyrevanced) for his work.
+15 -62
View File
@@ -6,86 +6,39 @@ from loguru import logger
from src.config import RevancedConfig
from src.downloader.factory import DownloaderFactory
from src.downloader.utils import download_revanced
from src.parser import Parser
from src.patches import Patches
from src.utils import AppNotFound, PatcherDownloadFailed
from src.utils import AppNotFound, PatchesJsonFailed, check_java
def main() -> None:
"""Entry point."""
from src.app import APP
env = Env()
config = RevancedConfig(env)
check_java(config.dry_run)
patcher = Patches(config)
logger.info(f"Will Patch only {config.apps}")
for app in config.apps:
logger.info(f"Trying to build {app}")
try:
download_revanced(config, patcher)
except PatcherDownloadFailed as e:
logger.error(f"Failed to download {e}")
sys.exit(1)
logger.info(f"Will Patch only {patcher.config.apps}")
for app in patcher.config.apps:
try:
logger.info("Trying to build %s" % app)
app = APP(app_name=app, config=config)
patcher = Patches(config, app)
parser = Parser(patcher, config)
app_all_patches, version, is_experimental = patcher.get_app_configs(app)
app_all_patches = patcher.get_app_configs(app)
patcher.include_exclude_patch(app, parser, app_all_patches)
downloader = DownloaderFactory.create_downloader(
app=app, patcher=patcher, config=config
app=app.app_name, patcher=patcher, config=config
)
downloader.download(version, app)
config.app_versions[app] = version
logger.info(f"Downloaded {app}, version {version}")
parser.patch_app(app=app, version=version, is_experimental=is_experimental)
downloader.download(app.app_version, app.app_name)
parser.patch_app(app)
except AppNotFound as e:
logger.info(f"Invalid app requested to build {e}")
except PatchesJsonFailed:
logger.exception("Patches.json not found")
except Exception as e:
logger.exception(f"Failed to build {app} because of {e}")
if len(config.alternative_youtube_patches) and "youtube" in config.apps:
for alternative_patch in config.alternative_youtube_patches:
parser = Parser(patcher, config)
app_all_patches, version, is_experimental = patcher.get_app_configs(
"youtube"
)
patcher.include_exclude_patch("youtube", parser, app_all_patches)
was_inverted = parser.invert_patch(alternative_patch)
if was_inverted:
logger.info(
f"Rebuilding youtube with inverted {alternative_patch} patch."
)
parser.patch_app(
app="youtube",
version=config.app_versions.get("youtube", "latest"),
is_experimental=is_experimental,
output_prefix="-" + alternative_patch + "-",
)
else:
logger.info(
f"Skipping Rebuilding youtube as {alternative_patch} patch was not found."
)
if len(config.alternative_youtube_music_patches) and "youtube_music" in config.apps:
for alternative_patch in config.alternative_youtube_music_patches:
parser = Parser(patcher, config)
app_all_patches, version, is_experimental = patcher.get_app_configs(
"youtube_music"
)
patcher.include_exclude_patch("youtube_music", parser, app_all_patches)
was_inverted = parser.invert_patch(alternative_patch)
if was_inverted:
logger.info(
f"Rebuilding youtube music with inverted {alternative_patch} patch."
)
parser.patch_app(
app="youtube_music",
version=config.app_versions.get("youtube_music", "latest"),
is_experimental=is_experimental,
output_prefix="-" + alternative_patch + "-",
)
else:
logger.info(
f"Skipping Rebuilding youtube music as {alternative_patch} patch was not found."
)
if __name__ == "__main__":
+103
View File
@@ -0,0 +1,103 @@
"""Class to represent apk to be patched."""
import concurrent
import hashlib
import pathlib
from concurrent.futures import ThreadPoolExecutor
from typing import Dict
from loguru import logger
from src.config import RevancedConfig
from src.utils import PatcherDownloadFailed, slugify
class APP(object):
"""Patched APK."""
def __init__(self, app_name: str, config: RevancedConfig):
self.app_name = app_name
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
)
self.integrations_dl = config.env.str(
f"{app_name}_INTEGRATION_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.exclude_request = config.env.list(f"{app_name}_EXCLUDE_PATCH".upper(), [])
self.include_request = config.env.list(f"{app_name}_INCLUDE_PATCH".upper(), [])
self.resource: Dict[str, str] = {}
self.no_of_patches = 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
)
self.download_patch_resources(config)
def get_output_file_name(self) -> str:
"""Get output file appended with version."""
return f"Re-{self.app_name}-{slugify(self.app_version)}-output.apk"
def set_recommended_version(self, version: str, exp: bool = False) -> None:
"""Update if cooking non-recommended."""
self.app_version = version
self.experiment = exp
def __str__(self: "APP") -> str:
attrs = vars(self)
return ", ".join([f"{key}: {value}" for key, value in attrs.items()])
@staticmethod
def download(url: str, config: RevancedConfig, assets_filter: str) -> str:
"""Downloader."""
from src.downloader.download import Downloader
url = url.strip()
if url.startswith("https://github"):
from src.downloader.github import Github
url = Github.patch_resource(url, assets_filter)[0]
extension = pathlib.Path(url).suffix
file_name = APP.generate_filename(url) + extension
Downloader(None, config).direct_download(url, file_name) # type: ignore
return file_name
def download_patch_resources(self, config: RevancedConfig) -> None:
"""Download resource for patching."""
logger.info("Downloading resources for patching.")
# Create a list of resource download tasks
download_tasks = [
("cli", self.cli_dl, config, ".*jar"),
("integrations", self.integrations_dl, config, ".*apk"),
("patches", self.patches_dl, config, ".*jar"),
("patches_json", self.patches_json_dl, config, ".*json"),
]
# Using a ThreadPoolExecutor for parallelism
with ThreadPoolExecutor(4) as executor:
futures = {
resource_name: executor.submit(self.download, *args)
for resource_name, *args in download_tasks
}
# Wait for all tasks to complete
concurrent.futures.wait(futures.values())
# Retrieve results from completed tasks
for resource_name, future in futures.items():
try:
self.resource[resource_name] = future.result()
except Exception as e:
raise PatcherDownloadFailed(f"An exception occurred: {e}")
@staticmethod
def generate_filename(url: str) -> str:
"""Get file name from url."""
encoded_url: str = hashlib.sha256(url.encode()).hexdigest()
return encoded_url
+21 -30
View File
@@ -1,23 +1,28 @@
"""Revanced Configurations."""
from pathlib import Path
from typing import Dict, List
from typing import List
from environs import Env
from requests import Session
from src.utils import default_build
default_cli = "https://github.com/revanced/revanced-cli/releases/latest"
default_patches = "https://github.com/revanced/revanced-patches/releases/latest"
default_patches_json = default_patches
default_integrations = (
"https://github.com/revanced/revanced-integrations/releases/latest"
)
class RevancedConfig(object):
"""Revanced Configurations."""
def __init__(self, env: Env) -> None:
self.app_versions: Dict[str, str] = {}
self.env = env
self.temp_folder = Path("apks")
self.session = Session()
self.session.headers["User-Agent"] = "anything"
self.build_extended = env.bool("BUILD_EXTENDED", False)
self.apk_mirror = "https://www.apkmirror.com"
self.upto_down = [
"spotify",
@@ -31,30 +36,9 @@ class RevancedConfig(object):
]
self.apk_pure = ["hex-editor", "androidtwelvewidgets"]
self.apk_sos = ["expensemanager", "candyvpn"]
self.keystore_name = env.str("KEYSTORE_FILE_NAME", "revanced.keystore")
self.ci_test = env.bool("CI_TEST", False)
self.apps = env.list("PATCH_APPS", default_build)
self.extended_apps: List[str] = ["youtube", "youtube_music", "microg", "reddit"]
self.rip_libs_apps: List[str] = ["youtube"]
self.normal_cli_jar = "revanced-cli.jar"
self.normal_patches_jar = "revanced-patches.jar"
self.normal_integrations_apk = "revanced-integrations.apk"
self.normal_options_json = "options.json"
self.cli_jar = (
f"inotia00-{self.normal_cli_jar}"
if self.build_extended
else self.normal_cli_jar
)
self.patches_jar = (
f"inotia00-{self.normal_patches_jar}"
if self.build_extended
else self.normal_patches_jar
)
self.integrations_apk = (
f"inotia00-{self.normal_integrations_apk}"
if self.build_extended
else self.normal_integrations_apk
)
self.rip_libs_apps: List[str] = []
self.apk_mirror_urls = {
"reddit": f"{self.apk_mirror}/apk/redditinc/reddit/",
"twitter": f"{self.apk_mirror}/apk/x-corp/twitter/",
@@ -89,11 +73,18 @@ class RevancedConfig(object):
key: value + value.split("/")[-2]
for key, value in self.apk_mirror_urls.items()
}
self.archs_to_build = env.list("ARCHS_TO_BUILD", [])
self.alternative_youtube_patches = env.list("ALTERNATIVE_YOUTUBE_PATCHES", [])
self.alternative_youtube_music_patches = env.list(
"ALTERNATIVE_YOUTUBE_MUSIC_PATCHES", []
)
self.existing_downloaded_apks = env.list("EXISTING_DOWNLOADED_APKS", [])
self.personal_access_token = env.str("PERSONAL_ACCESS_TOKEN", None)
self.dry_run = env.bool("DRY_RUN", False)
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_json_dl = env.str(
"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_archs_to_build = env.list("GLOBAL_ARCHS_TO_BUILD", [])
-4
View File
@@ -30,7 +30,6 @@ class ApkMirror(Downloader):
"p.notes:nth-child(3) > span:nth-child(1) > a:nth-child(1)"
).attributes["href"]
self._download(self.config.apk_mirror + href, f"{app}.apk")
logger.debug("Finished Extracting link and downloading")
def get_download_page(self, parser: LexborHTMLParser, main_page: str) -> str:
"""Function to get the download page in apk_mirror.
@@ -67,7 +66,6 @@ class ApkMirror(Downloader):
:param version: Version of the application to download
:return: Version of downloaded apk
"""
logger.debug(f"Trying to download {app},specific version {version}")
version = version.replace(".", "-")
main_page = f"{self.config.apk_mirror_version_urls.get(app)}-{version}-release/"
parser = LexborHTMLParser(
@@ -75,7 +73,6 @@ class ApkMirror(Downloader):
)
download_page = self.get_download_page(parser, main_page)
self.extract_download_link(download_page, app)
logger.debug(f"Downloaded {app} apk from apkmirror_specific_version")
def latest_version(self, app: str, **kwargs: Any) -> None:
"""Function to download whatever the latest version of app from
@@ -105,4 +102,3 @@ class ApkMirror(Downloader):
parser = LexborHTMLParser(self.config.session.get(main_page).text)
download_page = self.get_download_page(parser, main_page)
self.extract_download_link(download_page, app)
logger.debug(f"Downloaded {app} apk from apkmirror_specific_version in rt")
+17 -6
View File
@@ -1,5 +1,6 @@
"""Downloader Class."""
import os
from pathlib import Path
from queue import PriorityQueue
from time import perf_counter
from typing import Any, Tuple
@@ -23,14 +24,20 @@ class Downloader(object):
self.config = config
self.patcher = patcher
def _download(self, url: str, file_name: str) -> None:
if (
os.path.exists(self.config.temp_folder.joinpath(file_name))
or self.config.dry_run
):
@staticmethod
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}. File already exists or dry running."
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 self.file_status_check(
self.config.temp_folder.joinpath(file_name), self.config.dry_run, url
):
return
logger.info(f"Trying to download {file_name} from {url}")
self._QUEUE_LENGTH += 1
@@ -99,3 +106,7 @@ class Downloader(object):
self.specific_version(app, version)
else:
self.latest_version(app, **kwargs)
def direct_download(self, dl: str, file_name: str) -> None:
"""Download from DL."""
self._download(dl, file_name)
+10 -1
View File
@@ -1,7 +1,8 @@
"""Github Downloader."""
from typing import Dict
from typing import Dict, List
import requests
from lastversion import latest
from loguru import logger
from src.downloader.download import Downloader
@@ -41,3 +42,11 @@ class Github(Downloader):
download_url = response.json()["assets"][0]["browser_download_url"]
update_changelog(f"{owner}/{repo_name}", response.json())
self._download(download_url, file_name=app)
@staticmethod
def patch_resource(repo_url: str, assets_filter: str) -> list[str]:
"""Fetch patch resource from repo url."""
latest_resource_version: List[str] = latest(
repo_url, assets_filter=assets_filter, output_format="assets"
)
return latest_resource_version
-59
View File
@@ -1,63 +1,4 @@
"""Utility class."""
import os
from concurrent.futures import ThreadPoolExecutor, as_completed
from loguru import logger
from src.config import RevancedConfig
from src.patches import Patches
from src.utils import PatcherDownloadFailed
implement_method = "Please implement the method"
def download_revanced(config: RevancedConfig, patcher: Patches) -> None:
"""Download Revanced and Extended Patches, Integration and CLI."""
from src.downloader.factory import DownloaderFactory
if os.path.exists("changelog.md") and not config.dry_run:
logger.debug("Deleting old changelog.md")
os.remove("changelog.md")
assets = [
["revanced", "revanced-cli", config.normal_cli_jar],
["revanced", "revanced-integrations", config.normal_integrations_apk],
["revanced", "revanced-patches", config.normal_patches_jar],
]
if config.build_extended:
assets += [
["inotia00", "revanced-cli", config.cli_jar],
["inotia00", "revanced-integrations", config.integrations_apk],
["inotia00", "revanced-patches", config.patches_jar],
]
if (
"youtube" in config.apps
or "youtube_music" in config.apps
or "microg" in config.apps
):
if config.build_extended and "microg" in config.apps:
assets += [
["inotia00", "mMicroG", "microg.apk"],
]
else:
assets += [
["inotia00", "mMicroG", "microg-output.apk"],
]
downloader = DownloaderFactory.create_downloader(
app="patches", patcher=patcher, config=config
)
with ThreadPoolExecutor(7) as executor:
futures = [
executor.submit(
downloader.download,
version="latest",
app=repo[2],
**{"owner": repo[0], "name": repo[1]},
)
for repo in assets
]
for future in as_completed(futures):
try:
future.result()
except Exception as e:
raise PatcherDownloadFailed(f"An exception occurred: {e}")
logger.info("Downloaded revanced microG ,cli, integrations and patches.")
+15 -32
View File
@@ -6,9 +6,10 @@ from typing import List
from loguru import logger
from src.app import APP
from src.config import RevancedConfig
from src.patches import Patches
from src.utils import possible_archs, slugify
from src.utils import possible_archs
class Parser(object):
@@ -70,63 +71,45 @@ class Parser(object):
# noinspection IncorrectFormatting
def patch_app(
self,
app: str,
version: str,
is_experimental: bool = False,
output_prefix: str = "-",
app: APP,
) -> None:
"""Revanced APP Patcher.
:param app: Name of the app
:param version: Version of the application
:param is_experimental: Whether to enable experimental support
:param output_prefix: Prefix to add to the output apks file name
"""
cli = self.config.normal_cli_jar
patches = self.config.normal_patches_jar
integrations = self.config.normal_integrations_apk
options = self.config.normal_options_json
if self.config.build_extended and app in self.config.extended_apps:
cli = self.config.cli_jar
patches = self.config.patches_jar
integrations = self.config.integrations_apk
args = [
"-jar",
cli,
app.resource["cli"],
"-a",
app + ".apk",
app.app_name + ".apk",
"-b",
patches,
app.resource["patches"],
"-m",
integrations,
app.resource["integrations"],
"-o",
f"Re-{app}-{slugify(version)}{output_prefix}output.apk",
app.get_output_file_name(),
"--keystore",
self.config.keystore_name,
app.keystore_name,
"--options",
options,
"options.json",
]
if is_experimental:
if app.experiment:
logger.debug("Using experimental features")
args.append("--experimental")
args[1::2] = map(lambda i: self.config.temp_folder.joinpath(i), args[1::2])
args[1::2] = map(self.config.temp_folder.joinpath, args[1::2])
if self.config.ci_test:
self.exclude_all_patches()
if self._PATCHES:
args.extend(self._PATCHES)
if (
self.config.build_extended
and len(self.config.archs_to_build) > 0
and app in self.config.rip_libs_apps
):
excluded = set(possible_archs) - set(self.config.archs_to_build)
if app.app_name in self.config.rip_libs_apps:
excluded = set(possible_archs) - set(app.archs_to_build)
for arch in excluded:
args.append("--rip-lib")
args.append(arch)
start = perf_counter()
logger.debug(
f"Sending request to revanced cli for building {app} revanced with args java {args}"
f"Sending request to revanced cli for building with args java {args}"
)
process = Popen(["java", *args], stdout=PIPE)
output = process.stdout
+34 -115
View File
@@ -1,13 +1,13 @@
"""Revanced Patches."""
import json
import subprocess
import os
from typing import Any, Dict, List, Tuple
from loguru import logger
from requests import Session
from src.app import APP
from src.config import RevancedConfig
from src.utils import AppNotFound, handle_response
from src.utils import AppNotFound, PatchesJsonFailed
class Patches(object):
@@ -50,18 +50,12 @@ class Patches(object):
"ml.docilealligator.infinityforreddit": "infinity",
"me.ccrama.redditslide": "slide",
"com.onelouder.baconreader": "bacon",
}
revanced_app_ids = {
key: (value, "_" + value) for key, value in _revanced_app_ids.items()
}
_revanced_extended_app_ids = {
"com.google.android.youtube": "youtube",
"com.google.android.apps.youtube.music": "youtube_music",
"com.mgoogle.android.gms": "microg",
"com.reddit.frontpage": "reddit",
}
revanced_extended_app_ids = {
key: (value, "_" + value) for key, value in _revanced_extended_app_ids.items()
revanced_app_ids = {
key: (value, "_" + value) for key, value in _revanced_app_ids.items()
}
@staticmethod
@@ -69,40 +63,20 @@ class Patches(object):
"""Return supported apps."""
return Patches._revanced_app_ids
@staticmethod
def check_java(dry_run: bool) -> None:
"""Check if Java17 is installed."""
try:
if dry_run:
return
jd = subprocess.check_output(
["java", "-version"], stderr=subprocess.STDOUT
).decode("utf-8")
jd = jd[1:-1]
if "Runtime Environment" not in 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")
except subprocess.CalledProcessError:
logger.debug("Java>= 17 Must be installed")
exit(-1)
def scrap_patches(self, file_name: str) -> Any:
"""Scrap Patches."""
if os.path.exists(file_name):
with open(file_name) as f:
patches = json.load(f)
return patches
raise PatchesJsonFailed()
# noinspection DuplicatedCode
def fetch_patches(self) -> None:
def fetch_patches(self, config: RevancedConfig, app: APP) -> None:
"""Function to fetch all patches."""
session = Session()
if self.config.dry_run:
logger.debug("fetching all patches from local file")
with open("patches.json") as f:
patches = json.load(f)
else:
url = "https://raw.githubusercontent.com/revanced/revanced-patches/main/patches.json"
logger.debug(f"fetching all patches from {url}")
response = session.get(url)
handle_response(response)
patches = response.json()
patches = self.scrap_patches(
f'{config.temp_folder}/{app.resource["patches_json"]}'
)
for app_name in (self.revanced_app_ids[x][1] for x in self.revanced_app_ids):
setattr(self, app_name, [])
setattr(self, "universal_patch", [])
@@ -122,47 +96,11 @@ class Patches(object):
p["app"] = compatible_package
p["version"] = version[-1] if version else "all"
getattr(self, app_name).append(p)
if self.config.dry_run:
extended_patches = patches
else:
if self.config.build_extended:
url = "https://raw.githubusercontent.com/inotia00/revanced-patches/revanced-extended/patches.json"
else:
url = "https://raw.githubusercontent.com/revanced/revanced-patches/main/patches.json"
response = session.get(url)
handle_response(response)
extended_patches = response.json()
for app_name in (
self.revanced_extended_app_ids[x][1] for x in self.revanced_extended_app_ids
):
setattr(self, app_name, [])
n_patches = len(getattr(self, f"_{app.app_name}"))
app.no_of_patches = n_patches
for patch in extended_patches:
for compatible_package, version in [
(x["name"], x["versions"]) for x in patch["compatiblePackages"]
]:
if compatible_package in self.revanced_extended_app_ids:
app_name = self.revanced_extended_app_ids[compatible_package][1]
p = {x: patch[x] for x in ["name", "description"]}
p["app"] = compatible_package
p["version"] = version[-1] if version else "all"
getattr(self, app_name).append(p)
for app_name, app_id in self.revanced_extended_app_ids.values():
n_patches = len(getattr(self, app_id))
logger.debug(f"Total patches in {app_name} are {n_patches}")
for app_name, app_id in self.revanced_app_ids.values():
n_patches = len(getattr(self, app_id))
logger.debug(f"Total patches in {app_name} are {n_patches}")
n_patches = len(getattr(self, "universal_patch"))
logger.debug(f"Total universal patches are {n_patches}")
def __init__(self, config: RevancedConfig) -> None:
self.config = config
self.check_java(self.config.dry_run)
self.fetch_patches()
if self.config.dry_run:
self.config.apps = list(self._revanced_app_ids.values())
def __init__(self, config: RevancedConfig, app: APP) -> None:
self.fetch_patches(config, app)
def get(self, app: str) -> Tuple[List[Dict[str, str]], str]:
"""Get all patches for the given app.
@@ -170,11 +108,7 @@ class Patches(object):
:param app: Name of the application
:return: Patches
"""
logger.debug("Getting patches for %s" % app)
app_names = {value[0]: value[1] for value in self.revanced_app_ids.values()}
app_names.update(
{value[0]: value[1] for value in self.revanced_extended_app_ids.values()}
)
if not (app_name := app_names.get(app)):
raise AppNotFound(app)
@@ -182,14 +116,13 @@ class Patches(object):
version = "latest"
try:
version = next(i["version"] for i in patches if i["version"] != "all")
logger.debug(f"Recommended Version for patching {app} is {version}")
except StopIteration:
pass
return patches, version
# noinspection IncorrectFormatting
def include_exclude_patch(
self, app: str, parser: Any, patches: List[Dict[str, str]]
self, app: APP, parser: Any, patches: List[Dict[str, str]]
) -> None:
"""Include and exclude patches for a given app.
@@ -197,34 +130,20 @@ class Patches(object):
:param parser: Parser Obj
:param patches: All the patches of a given app
"""
if self.config.build_extended and app in self.config.extended_apps:
excluded_patches = self.config.env.list(
f"EXCLUDE_PATCH_{app}_EXTENDED".upper(), []
)
included_patches = self.config.env.list(
f"INCLUDE_PATCH_{app}_EXTENDED".upper(), []
)
else:
excluded_patches = self.config.env.list(f"EXCLUDE_PATCH_{app}".upper(), [])
included_patches = self.config.env.list(f"INCLUDE_PATCH_{app}".upper(), [])
for patch in patches:
normalized_patch = patch["name"].lower().replace(" ", "-")
parser.include(
normalized_patch
) if normalized_patch not in excluded_patches else parser.exclude(
) if normalized_patch not in app.exclude_request else parser.exclude(
normalized_patch
)
for normalized_patch in included_patches:
for normalized_patch in app.include_request:
parser.include(normalized_patch) if normalized_patch not in getattr(
self, "universal_patch", []
) else ()
excluded = parser.get_excluded_patches()
if excluded:
logger.debug(f"Excluded patches {excluded} for {app}")
else:
logger.debug(f"No excluded patches for {app}")
logger.info(app)
def get_app_configs(self, app: str) -> Tuple[List[Dict[str, str]], str, bool]:
def get_app_configs(self, app: "APP") -> List[Dict[str, str]]:
"""Get Configurations for a given app.
:param app: Name of the application
@@ -232,15 +151,15 @@ class Patches(object):
experimental
"""
experiment = False
total_patches, recommended_version = self.get(app=app)
env_version = self.config.env.str(f"{app}_VERSION".upper(), None)
if env_version:
logger.debug(f"Picked {app} version {env_version} from env.")
total_patches, recommended_version = self.get(app=app.app_name)
if app.app_version:
logger.debug(f"Picked {app} version {app.app_version:} from env.")
if (
env_version == "latest"
or env_version > recommended_version
or env_version < recommended_version
app.app_version == "latest"
or app.app_version > recommended_version
or app.app_version < recommended_version
):
experiment = True
recommended_version = env_version
return total_patches, recommended_version, experiment
recommended_version = app.app_version
app.set_recommended_version(recommended_version, experiment)
return total_patches
+26
View File
@@ -1,5 +1,6 @@
"""Utilities."""
import re
import subprocess
from typing import Dict
from loguru import logger
@@ -50,6 +51,12 @@ class PatcherDownloadFailed(Exception):
pass
class PatchesJsonFailed(ValueError):
"""Patches failed."""
pass
def handle_response(response: Response) -> None:
"""Handle Get Request Response."""
response_code = response.status_code
@@ -76,3 +83,22 @@ def slugify(string: str) -> str:
string = string.strip("-")
return string
def check_java(dry_run: bool) -> None:
"""Check if Java>=17 is installed."""
try:
if dry_run:
return
jd = subprocess.check_output(
["java", "-version"], stderr=subprocess.STDOUT
).decode("utf-8")
jd = jd[1:-1]
if "Runtime Environment" not in 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")
except subprocess.CalledProcessError:
logger.debug("Java>= 17 Must be installed")
exit(-1)