Merge pull request #1 from SinTan1729/python-rewrite

Python rewrite
This commit is contained in:
Sayantan Santra 2023-08-11 00:05:13 +00:00 committed by GitHub
commit 5982b5aa5f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 684 additions and 596 deletions

4
.gitignore vendored
View file

@ -7,3 +7,7 @@ build/
.editorconfig
patches.txt
.vscode/
.directory
.venv/
__pycache__/
ReVancedBuilder.egg-info/

View file

@ -1,47 +0,0 @@
# Customizing ReVanced Builds
**Please read the following information before beginning.**
By default the script will build ReVanced with ALL default* patches. Copy `chosen_patches.txt` inside your provided working directory and edit it to customize your build of ReVanced.
*Default: All patches except those which have to be ***included*** explicitly, i.e, using the `-i` flag while manually using the ReVanced CLI
## !IMPORTANT!
1. Each patch name MUST start from a NEWLINE AND there should be only ONE patch PER LINE
2. DO NOT add any other type of symbol or character, it will break the script! You have been warned!
3. Anything starting with a hash (`#`) will be ignored. Also, do not add hash or any other character after a patch's name
4. Both YT Music ReVanced & YT ReVanced are supported
5. DO NOT add `microg-patch` to the list of excluding patches.
6. `patches.txt` contains some predefined lines starting with `#`. DO NOT remove them.
## Example
Example content of `patches.txt`:
- Exclude pure black theme and keep `create` button:
```
amoled
disable-create-button
```
- Exclude patches for both Music & YouTube (order doesn't matter)
```
amoled
exclusive-background-playback
disable-create-button
premium-heading
tasteBuilder-remover
```
- Include patches for both Music & YouTube (order doesn't matter)
```
compact-header
hdr-auto-brightness
autorepeat-by-default
enable-debugging
force-vp9-codec
enable-wide-searchbar
```
## List of Available Patches
Refer to Official ReVanced [list of available patches](https://github.com/revanced/revanced-patches#list-of-available-patches).

View file

@ -1,12 +1,19 @@
# Revanced Builder
This repo will allow one to build [ReVanced](https://github.com/revanced/) apps automatically and post it to a telegram channel to access and possibly share the builds with friends. It uses [Gotify](https://gotify.net), [ntfy.sh](https://ntfy.sh) or [telegram.sh](https://github.com/fabianonline/telegram.sh) to send messages and [telegram-upload](https://github.com/Nekmo/telegram-upload) to upload files (optionally, disabled out by default). Make sure that `Java >=17` is installed and selected as default.
This repo will allow one to build [ReVanced](https://github.com/revanced/) apps automatically and send notifications and possibly share the builds with friends. It uses [Gotify](https://gotify.net), [ntfy.sh](https://ntfy.sh) or [telegram.sh](https://github.com/fabianonline/telegram.sh) to send messages. Make sure that `Java >=17` is installed and selected as default.
## Installation
Recommended way is to use [`pipx`](https://github.com/pypa/pipx) to install the program.
```
pipx install git+https://github.com/SinTan1729/ReVancedBuilder
```
## How to use
Just run `./build_revanced <working-directory> (force/clean/experimental/checkonly/buildonly)`. Might be a good idea to set it up to run periodically. There are a few ways of doing it.
Just run `ReVancedBuilder <working-directory> (force/experimental/checkonly/buildonly)`.
It might be a good idea to set it up to run periodically. There are a few ways of doing it.
1. Just drop it inside `/etc/cron.daily/`.
1. To make it run at a specific time (6AM in the example) using `cron`, put this in your `crontab`:
```
0 6 * * * <full-script-location> <full-working-directory-location>
0 6 * * * <program-full-location> <full-working-directory-location>
```
1. The exact same thing as in 2 can be achieved using `systemd` timers instead. Create the following files.
```
@ -22,7 +29,7 @@ Just run `./build_revanced <working-directory> (force/clean/experimental/checkon
User=<user>
Group=<group>
Environment="_JAVA_OPTIONS=-Xmx512m"
ExecStart=<full-script-location> <full-working-directory-location>
ExecStart=<program-full-location> <full-working-directory-location>
```
```
/etc/systemd/system/revanced-builder.timer
@ -42,19 +49,12 @@ Just run `./build_revanced <working-directory> (force/clean/experimental/checkon
```
## Notes
- The following programs are needed to run this script. Make sure that you have them in your `$PATH`.
```
htmlq jq wget java curl
```
- To enable build for a particular apk, copy the `build_settings` file to your working directory and modify it to suit your needs.
- The script will download the **automatically selected compatible version** (using compatibility of patches as listed [here](https://github.com/revanced/revanced-patches#list-of-available-patches)) of Youtube on APKPure, **NOT** latest official version on Google Play.
- If you installed it using `pipx`, you can figure out the full location of the program by running `which ReVancedBuilder`.
- This app needs some config files to run. Download all the config files inside `exampl_configs` directory, namely `build_config`, `chosen_patches` (optional), and `notification_config` (optional, needed only if you want to send notifications) and move them to your working directory. Then, you should modify these files to your liking.
- The script will download the **automatically selected compatible version** (unless version is specified in `build_config`) (using compatibility of patches as listed [here](https://github.com/revanced/revanced-patches#list-of-available-patches)) of Youtube on APKPure, **NOT** latest official version on Google Play.
- Under **NO CIRCUMSTANCES** any APKs will be uploaded to this repository to avoid DMCA.
- If you enable the Gotify, ntfy or telegram notifications or uploads, make sure to fill up the config options inside the `build_settings` file. For more information about the config, take at look at the repos of `telegram.sh` and `telegram-upload` provided above.
- It can also run a post script (if exists) called `post_script.sh`. The `timestamp` is passed as `$1`.
- In the current configuration, the script only builds YouTube ReVanced and YouTube Music ReVanced (both nonroot), but it's easy to add support for any other ReVanced app. The code for root builds is included but disabled by default.
- If you enable telegram notifications, make sure to fill up the config options inside the `build_config` file. For more information about the config, take at look at the repos of `telegram.sh` and `telegram-upload` provided above.
- It can also run a post script (if exists), specified in the `build_config` file. The `timestamp` is passed as `$1`.
- In the current configuration, the script only builds YouTube ReVanced and YouTube Music ReVanced (both nonroot), but it's easy to add support for any other ReVanced app using the `build_config` file. The config files are self-explanatory.
- All the packages are pulled from [APKPure](https://apkpure.com) and GitHub (the `revanced/*` repos).
## Customize your build
If you wish to continue with the default settings, you may skip this step.
By default this will build ReVanced with ALL available patches. Follow [this guide](PATCHES_GUIDE.md) to exclude/customizing patches for your build.

View file

@ -1,324 +0,0 @@
#!/usr/bin/env bash
# Run only one instance of this script at one time
[ "${BKLOCKER}" != "running" ] && exec env BKLOCKER="running" flock -en "/tmp/revanced-builder.lock" "$0" "$@" || :
# Get timestamp
timestamp=$(date '+%Y%m%d%H%M%S')
# Log everything to a logfile inside logs/
log_file="$1/logs/$timestamp.log"
[ -d "$1" ] && mkdir -p "$1/logs" && exec > >(tee "$log_file") 2>&1
# Set working directory and current directory
if [ -d "$1" ]; then
WDIR="$1"
else
echo "Working directory not provided"
exit 1
fi
# File containing all patches
patch_file="$WDIR/chosen_patches.txt"
# Returns if $1 is less than $2
ver_less_than() {
# Strip letters from version name
ver1=$(echo $1 | sed 's/[a-zA-Z]*//g')
ver2=$(echo $2 | sed 's/[a-zA-Z]*//g')
[ $(echo $ver1$'\n'$ver2 | sort -V | tail -n1) != $ver1 ] && echo true || echo false
}
# Make sure to work in the script directory
SDIR="$(dirname -- "$(readlink -f -- "$0")")"
cd "$SDIR"
# Read the settings
if [ -f "$WDIR/build_settings" ]; then
source "$WDIR/build_settings"
else
if [ -f "./build_settings"]; then
cp ./build_settings "$WDIR/build_settings"
source ./build_settings
else
echo "Could not find the build_settings file!"
fi
fi
# Get line numbers where included & excluded patches start from.
# We rely on the hardcoded messages to get the line numbers using grep
excluded_start="$(grep -n -m1 'EXCLUDE PATCHES' "$patch_file" | cut -d':' -f1)"
included_start="$(grep -n -m1 'INCLUDE PATCHES' "$patch_file" | cut -d':' -f1)"
# Get everything but hashes from between the EXCLUDE PATCH & INCLUDE PATCH line
# Note: '^[^#[:blank:]]' ignores starting hashes and/or blank characters i.e, whitespace & tab excluding newline
excluded_patches="$(tail -n +$excluded_start $patch_file | head -n "$((included_start - excluded_start))" | grep '^[^#[:blank:]]')"
# Get everything but hashes starting from INCLUDE PATCH line until EOF
included_patches="$(tail -n +$included_start $patch_file | grep '^[^#[:blank:]]')"
# Array for storing patches
declare -a patches
# Required artifacts in the format repository-name_filename
artifacts="revanced/revanced-cli:revanced-cli.jar revanced/revanced-integrations:revanced-integrations.apk revanced/revanced-patches:revanced-patches.jar inotia00/VancedMicroG:microg.apk"
## Functions
# Function for populating patches array, using a function here reduces redundancy & satisfies DRY principals
populate_patches() {
# Note: <<< defines a 'here-string'. Meaning, it allows reading from variables just like from a file
while read -r patch; do
patches+=("$1 $patch")
done <<<"$2"
}
## Main
# cleanup to fetch new revanced on next run
if [[ "$2" == "clean" ]]; then
rm -f revanced-cli.jar revanced-integrations.apk revanced-patches.jar
exit
fi
if [[ "$2" == "experimental" ]]; then
EXPERIMENTAL="--experimental"
fi
# Set flag to determine if a build should happen or not
flag=false
check_flag=false
# Get inside the working directory
cd "$WDIR"
echo "$(date) | Starting check..."
if [[ $2 != buildonly ]]; then
# Create a new versions file, if needed
[ -f versions.json ] || echo "{}" >versions.json
cp versions.json versions-new.json
# Fetch all the dependencies
try=0
while :; do
try=$(($try + 1))
[ $try -gt 10 ] && echo "API error!" && exit 2
curl -s -X 'GET' 'https://releases.revanced.app/tools' -H 'accept: application/json' -o latest_versions.json
cat latest_versions.json | jq -e '.error' >/dev/null || break
echo "API failure, trying again. $((10 - $try)) tries left..."
sleep 10
done
for artifact in $artifacts; do
#Check for updates
repo=$(echo $artifact | cut -d ':' -f1)
name=$(echo $artifact | cut -d ':' -f2)
basename=$(echo $repo | cut -d '/' -f2)
echo "Checking $basename"
version_present=$(jq -r ".\"$basename\"" versions.json)
[[ "$version_present" == "null" ]] && version_present=0
data="$(jq -r ".tools[] | select((.repository == \"$repo\") and (.content_type | contains(\"archive\")))" latest_versions.json)"
[[ $name == microg.apk ]] && version=$(curl -s "https://api.github.com/repos/$repo/releases/latest" | jq -r '.tag_name') || version=$(echo "$data" | jq -r '.version')
if [[ $(ver_less_than $version_present $version) == true || ! -f $name || $2 == force ]]; then
if [[ $2 == checkonly ]]; then
echo "[checkonly] $basename has an update ($version_present -> $version)"
check_flag=true
continue
fi
echo "Downloading $name"
[[ $name == microg.apk && -f $name && $2 != force ]] && microg_updated=true
# shellcheck disable=SC2086,SC2046
[[ $name == microg.apk ]] && download_link="https://github.com/$repo/releases/latest/download/$name" || download_link="$(echo "$data" | jq -r '.browser_download_url')"
curl -sLo "$name" "$download_link"
jq ".\"$basename\" = \"$version\"" versions-new.json >versions-tmp.json && mv versions-tmp.json versions-new.json
echo "Upgraded $basename from $version_present to $version"
flag=true
fi
done
[[ ! -f com.google.android.youtube.apk || ! -f com.google.android.apps.youtube.music.apk ]] && flag=true
# Exit if no updates happened
if [[ $flag == false && $2 != force ]]; then
if [[ $check_flag == false ]]; then
echo "Nothing to update"
else
"$SDIR/download_apk.sh" "$WDIR" checkonly
fi
echo "--------------------"$'\n'"--------------------"
exit
fi
# Download required apk files
"$SDIR/download_apk.sh" "$WDIR"
fi
# If the variables are NOT empty, call populate_patches with proper arguments
[[ ! -z "$excluded_patches" ]] && populate_patches "-e" "$excluded_patches"
[[ ! -z "$included_patches" ]] && populate_patches "-i" "$included_patches"
# Variable to flag errors
error=0
# Functions for building the APKs
build_yt_nonroot() {
echo "************************************"
echo "Building YouTube APK"
echo "************************************"
if [ -f "com.google.android.youtube.apk" ]; then
echo "Building Non-root APK"
java -jar revanced-cli.jar -m revanced-integrations.apk -b revanced-patches.jar \
${patches[@]} \
$EXPERIMENTAL \
-a com.google.android.youtube.apk -o revanced-yt-nonroot.apk
else
echo "Cannot find YouTube APK, skipping build"
fi
echo ""
echo "************************************"
# Rename files
mv revanced-yt-nonroot.apk YouTube_ReVanced_nonroot_$timestamp.apk || error=1
}
build_yt_root() {
echo "************************************"
echo "Building YouTube APK"
echo "************************************"
if [ -f "com.google.android.youtube.apk" ]; then
echo "Building Root APK"
java -jar revanced-cli.jar -m revanced-integrations.apk -b revanced-patches.jar --mount \
-e microg-support ${patches[@]} \
$EXPERIMENTAL \
-a com.google.android.youtube.apk -o revanced-yt-root.apk
else
echo "Cannot find YouTube APK, skipping build"
fi
echo ""
echo "************************************"
# Rename files
mv revanced-yt-root.apk YouTube_ReVanced_root_$timestamp.apk || error=1
}
build_ytm_nonroot() {
echo "Building YouTube Music APK"
echo "************************************"
if [ -f "com.google.android.apps.youtube.music.apk" ]; then
echo "Building Non-root APK"
java -jar revanced-cli.jar -m revanced-integrations.apk -b revanced-patches.jar \
${patches[@]} \
$EXPERIMENTAL \
-a com.google.android.apps.youtube.music.apk -o revanced-ytm-nonroot.apk
else
echo "Cannot find YouTube Music APK, skipping build"
fi
# Rename files
mv revanced-ytm-nonroot.apk YouTube_Music_ReVanced_nonroot_$timestamp.apk || error=1
}
build_ytm_root() {
echo "Building YouTube Music APK"
echo "************************************"
if [ -f "com.google.android.apps.youtube.music.apk" ]; then
echo "Building Root APK"
java -jar revanced-cli.jar -m revanced-integrations.apk -b revanced-patches.jar --mount \
-e microg-support ${patches[@]} \
$EXPERIMENTAL \
-a com.google.android.apps.youtube.music.apk -o revanced-ytm-root.apk
else
echo "Cannot find YouTube Music APK, skipping build"
fi
# Rename files
mv revanced-ytm-root.apk YouTube_Music_ReVanced_root_$timestamp.apk || error=1
}
telegram_send_msg() {
# telegram.sh uses bot account, but it supports formatted messages
[[ "$TELEGRAM_TOKEN" == "" || "$TELEGRAM_CHAT" == "" ]] && echo "Please provide valid channel address in the settings!"
./telegram.sh -t "$TELEGRAM_TOKEN" -c "$TELEGRAM_CHAT" -T "⚙⚙⚙ Build Details ⚙⚙⚙" -M "$1"$'\n'"Timestamp: $timestamp"$'\n'"⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯"
}
gotify_send_msg() {
curl -s -X POST "$GOTIFY_URL/message?token=$GOTIFY_TOKEN" \
-F "title=⚙⚙⚙ Build Details ⚙⚙⚙" -F "message=$1" -F "priority=5"
}
ntfy_send_msg() {
curl -s -H "Icon: https://upload.wikimedia.org/wikipedia/commons/thumb/4/40/Revanced-logo-round.svg/240px-Revanced-logo-round.svg.png" \
-H "Title: ⚙⚙⚙ ReVanced Build ⚙⚙⚙" \
-d "$1" \
"$NTFY_URL/$NTFY_TOPIC"
}
# Check the config and build accordingly
$YT_NONROOT && build_yt_nonroot
$YT_ROOT && build_yt_root
$YTM_NONROOT && build_ytm_nonroot
$YTM_ROOT && build_ytm_root
# Send telegram message about the new build
if [ $error == 1 ]; then
echo "There was an error while building!"
msg="There was an error during the build process! Please take a look at the logs."$'\n'"Timestamp: $timestamp"
$TG_NOTIFICATIONS && telegram_send_msg "$msg"
$GOTIFY_NOTIFICATIONS && gotify_send_msg "$msg"
$NTFY_NOTIFICATIONS && ntfy_send_msg "$msg"
[[ $2 != buildonly ]] && mv versions-new.json versions-fail.json || rm versions-new.json
exit 4
else
mv versions.json versions-old.json
mv versions-new.json versions.json
fi
if $TG_UPLOAD; then
echo "Uploading to telegram"
# telegram-upload uses personal account, hence bypassing 50 MB max upload limit of bots
[ "$CHANNEL_ADDRESS" == "" ] && echo "Please provide valid channel address in the settings!"
/home/sintan/.local/bin/telegram-upload YouTube_ReVanced_nonroot_$timestamp.apk YouTube_Music_ReVanced_nonroot_$timestamp.apk --to "$CHANNEL_ADDRESS" --caption "" && sent=true
fi
# Create the message to be sent
msg=$(cat versions.json | tail -n+2 | head -n-1 | cut -c3- | sed "s/\"//g" | sed "s/,//g" | sed "s/com.google.android.apps.youtube.music/YouTube Music/" |
sed "s/com.google.android.youtube/YouTube/" | sed "s/VancedMicroG/Vanced microG/" | sed "s/revanced-/ReVanced /g" | sed "s/patches/Patches/" |
sed "s/cli/CLI/" | sed "s/integrations/Integrations/" | awk 1 ORS=$'\n') # I know, it's a hacky solution
if $TG_NOTIFICATIONS; then
echo "Sending messages to telegram"
telegram_send_msg "$msg"
[ $microg_updated ] && telegram_send_msg "_An update of microg was published._"
fi
if $GOTIFY_NOTIFICATIONS; then
echo "Sending messages to Gotify"
MESSAGE="$msg"$'\n'"Timestamp: $timestamp"
gotify_send_msg "$MESSAGE"
[ $microg_updated ] && gotify_send_msg "An update of microg was published."
fi
if $NTFY_NOTIFICATIONS; then
echo "Sending messages to ntfy.sh"
MESSAGE="$msg"$'\n'"Timestamp: $timestamp"
ntfy_send_msg "$MESSAGE"
[ $microg_updated ] && ntfy_send_msg "An update of microg was published."
fi
# Do some cleanup, keep only the last 3 build's worth of files and a week worth of logs
mkdir -p archive
mv *ReVanced_*_$timestamp.apk archive/
find ./archive -maxdepth 1 -type f -printf '%Ts\t%P\n' |
sort -rn |
tail -n +7 |
cut -f2- |
xargs -r -I {} rm "./archive/{}"
find ./logs -mtime +7 -exec rm {} \;
# Run a custom post script, if available
[ -f post_script.sh ] && ./post_script.sh $timestamp
echo "Done!"$'\n'"************************************"

View file

@ -1,45 +0,0 @@
# These are the currently supported apps that can be built
# Make a copy of this to your working directory
# Then change the default values to enable/disable building them
YT_NONROOT=true
YTM_NONROOT=true
YT_ROOT=false
YTM_ROOT=false
# You can provide versions of apk for the builds
# If anything nonempty is given, automatic version resolution
# will be disabled
# It's your job to make sure that the version is available
# in APKPure
YT_VERSION=
YTM_VERSION=
# Settings for sending Telegram notification using telegram.sh
# In case you decide to use it, please put valid config in the
# TOKEN and CHAT fields
# Check out README for instructions
TG_NOTIFICATIONS=false
TELEGRAM_TOKEN=""
TELEGRAM_CHAT=""
# Settings for uploading the files through telegram-upload
# In case you decide to use it, please put valid config in the
# CHANNEL_ADDRESS field
# Check out README for instructions
TG_UPLOAD=false
CHANNEL_ADDRESS=""
# Settings for sending Gotify notifications
# In case you decide to use it, please put valid config in the
# URL and TOKEN fields
# Check out README for instructions
GOTIFY_NOTIFICATIONS=false
GOTIFY_URL="https://push.example.com"
GOTIFY_TOKEN=""
# Settings for sending ntfy.sh notifications
# In case you decide to use it, please put valid config in the
# URL and TOPIC fields
NTFY_NOTIFICATIONS=false
NTFY_URL="https://ntfy.sh"
NTFY_TOPIC=""

View file

@ -1,3 +0,0 @@
# EXCLUDE PATCHES FROM BELOW. DO NOT REMOVE THIS LINE
# INCLUDE PATCHES FROM BELOW. DO NOT REMOVE THIS LINE

View file

@ -1,160 +0,0 @@
#!/usr/bin/env bash
declare -A apks
apks["com.google.android.youtube"]=dl_yt
apks["com.google.android.apps.youtube.music"]=dl_ytm
flag=$2
# Read the settings
source "$1/build_settings"
## Functions
# Wget user agent
WGET_HEADER="User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0"
# Wget function
req() { wget -nv -4 -O "$2" --header="$WGET_HEADER" "$1"; }
# Returns true if $1 is less than $2
ver_less_than() {
[[ ${1:0:1} == "v" ]] && var1=${1:1} || var1=$1
[[ ${2:0:1} == "v" ]] && var2=${2:1} || var2=$2
[[ $(echo $var1$'\n'$var2 | sort -V | tail -n1) != $var1 ]] && echo true || echo false
}
# APKPure Download function
dl_apkpure() {
version="$1"
app="$2"
apkpure_appname="$3"
$hard_vers && best_match="$version" || best_match="$(apkpure_best_match $version $app $apkpure_appname)"
# if [[ "$version" == "$best_match" || "$version" == "latest" ]]; then
# echo "Downloading version $best_match from APKPure"
# else
# echo "Unable to get version $version, downloading version $best_match instead"
# fi
vers_code="$(req https://apkpure.com/$apkpure_appname/$app/versions - | htmlq --attribute data-dt-versioncode 'a[data-dt-version="'$version'"][data-dt-apkid^="b\/APK\/"]')"
url="https://d.apkpure.com/b/APK/$app?versionCode=$vers_code"
req "$url" "$app.apk"
echo "$url"
}
# Get the best match even if the desired version isn't there
# OUtputs the latest version with supplied version 0
apkpure_best_match() {
version="$1"
app="$2"
apkpure_appname="$3"
vers_list=$(req https://apkpure.com/$apkpure_appname/$app/versions - | htmlq --attribute data-dt-version 'a[data-dt-apkid^="b\/APK\/"]')
if [[ "$version" == "latest" ]]; then
match="$(echo "$vers_list" | head -1)"
elif $(echo "$vers_list" | grep -q "$version"); then
match="$version"
else
match="$(echo "$vers_list"$'\n'"$version" | sort -V | grep -B 1 "$version" | head -1)"
fi
echo "$match"
}
# Downloading youtube
dl_yt() {
appname=com.google.android.youtube
$hard_vers || version="$(apkpure_best_match "$version" $appname youtube)"
if [[ ! $(ver_less_than "$version_present" "$version") && -f $appname.apk ]]; then
echo "Version $version is already present"
return
fi
if [[ $flag == checkonly ]]; then
echo "[checkonly] YouTube has an update ($version_present -> $version)"
return
fi
echo "Downloading YouTube"
echo "Choosing version $version"
declare -r dl_url=$(dl_apkpure "$version" $appname youtube)
echo "YouTube version: $version"
echo "downloaded from: [APKMirror - YouTube]($dl_url)"
jq ".\"$apk\" = \"$version\"" versions.json >versions-tmp.json && mv versions-tmp.json versions-new.json
}
# Downloading youtube music
dl_ytm() {
appname=com.google.android.apps.youtube.music
$hard_vers || version="$(apkpure_best_match "$version" $appname youtube-music)"
if [[ ! $(ver_less_than "$version_present" "$version") && -f $appname.apk ]]; then
echo "Version $version is already present"
return
fi
if [[ $flag == checkonly ]]; then
echo "[checkonly] YouTube Music has an update ($version_present -> $version)"
return
fi
echo "Downloading YouTube Music"
echo "Choosing version '${version}'"
# declare -r dl_url=$(dl_apkpure "$version" $appname youtube-music)
dl_apkpure "$version" $appname youtube-music
echo "YouTube Music version: $version"
echo "downloaded from: [APKMirror - YouTube Music]($dl_url)"
jq ".\"$apk\" = \"$version\"" versions.json >versions-tmp.json && mv versions-tmp.json versions-new.json
}
# Get into the build directory
if [ -d "$1" ]; then
cd "$1"
else
echo "Working directory not provided"
exit -1
fi
## Main
try=0
while :; do
try=$(($try + 1))
[ $try -gt 10 ] && echo "API error!" && exit 3
curl -X 'GET' 'https://releases.revanced.app/patches' -H 'accept: application/json' -o patches.json
cat patches.json | jq -e '.error' >/dev/null 2>&1 || break
echo "API failure, trying again. $((10 - $try)) tries left..."
sleep 10
done
for apk in "${!apks[@]}"; do
# Skip if app not specified for build
[[ "$apk" == "com.google.android.youtube" && "$YT_NONROOT" == false && "$YT_ROOT" == false ]] && continue
[[ "$apk" == "com.google.android.apps.youtube.music" && "$YTM_NONROOT" == false && "$YTM_ROOT" == false ]] && continue
echo "Checking $apk"
if [[ "$apk" == "com.google.android.youtube" && "$YT_VERSION" != "" ]]; then
version="$YT_VERSION"
echo "Using version $version for $apk given in build_settings"
hard_vers=true
elif [[ "$apk" == "com.google.android.apps.youtube.music" && "$YTM_VERSION" != "" ]]; then
version="$YTM_VERSION"
echo "Using version $version for $apk given in build_settings"
hard_vers=true
else
echo "Figuring out best version for $apk"
supported_vers="$(jq -r '.[].compatiblePackages[] | select(.name == "'$apk'") | .versions | last' patches.json)"
version=0
for vers in $supported_vers; do
[ $vers != "null" ] && [[ $(ver_less_than $vers $version) == true || $version == 0 ]] && version=$vers
done
hard_vers=false
fi
version_present=$(jq -r ".\"$apk\"" versions.json)
[[ -z "$version_present" || "$version" == "null" ]] && version_present=0
[[ "$version" == "0" ]] && version=latest
[[ "$version_present" != "$version" || ! -f $apk.apk || $2 == force ]] && ${apks[$apk]} || echo "Recommended version ($version_present) of "$apk" is already present"
done

View file

@ -0,0 +1,51 @@
# List all the applications to be built in their separate sections.
# The version part is optional. If provided, automatic version determination
# will be turned off. In that case, you're responsible for ensuring that the
# version is present in APKPure.com
[youtube_nonroot]
build = true
pretty_name = YouTube
apk = com.google.android.youtube
apkpure_appname = youtube
root = false
# Timestamp and extension will be added automatically
output_name = YouTube_ReVanced_nonroot
keystore = revanced-yt-nonroot.keystore
# version = version
[youtube_root]
build = false
pretty_name = YouTube (root)
apk = com.google.android.youtube
apkpure_appname = youtube
root = true
# Timestamp and extension will be added automatically
output_name = YouTube_ReVanced_root
keystore = revanced-yt-root.keystore
# version = "version"
[youtube_music]
build = true
pretty_name = YouTube Music
apk = com.google.android.apps.youtube.music
apkpure_appname = youtube-music
root = false
# Timestamp and extension will be added automatically
output_name = YouTube_Music_ReVanced_nonroot
keystore = revanced-ytm-nonroot.keystore
# version = version
[youtube_music_root]
build = false
pretty_name = YouTube Music (root)
apk = com.google.android.apps.youtube.music
apkpure_appname = youtube-music
root = true
# Timestamp and extension will be added automatically
output_name = YouTube_Music_ReVanced_root
keystore = revanced-ytm-root.keystore
# version = version
[post_script]
# file = post_script.sh

View file

@ -0,0 +1,4 @@
[patches]
# Both have to be comma separated lists of patches
included = []
excluded = []

View file

@ -0,0 +1,25 @@
[telegram]
# Settings for sending Telegram notification using telegram.sh
# In case you decide to use it, please put valid config in the
# TOKEN and CHAT fields
# Check out README for instructions
# enabled = true
# chat = url
# token = token
[gotify]
# Settings for sending Gotify notifications
# In case you decide to use it, please put valid config in the
# URL and TOKEN fields
# Check out README for instructions
# enabled = true
# URL = url
# token = token
[ntfy]
# Settings for sending ntfy.sh notifications
# In case you decide to use it, please put valid config in the
# URL and TOPIC fields
enabled = false
url = url
topic = topic

18
pyproject.toml Normal file
View file

@ -0,0 +1,18 @@
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "ReVancedBuilder"
authors = [{ name = "Sayantan Santra", email = "sayantan.santra689@gmail.com" }]
description = "A tool to automatically build latest releases of ReVanced apps"
readme = "README.md"
requires-python = ">=3.10"
keywords = ["revanced", "patch"]
license = { file = "LICENSE" }
classifiers = ["Programming Language :: Python :: 3"]
dependencies = ["requests", "packaging", "bs4"]
version = "1.0"
[project.scripts]
ReVancedBuilder = "ReVancedBuilder:ReVancedBuilder"

View file

@ -0,0 +1,120 @@
#!/usr/bin/env python3\
# SPDX-FileCopyrightText: 2023 Sayantan Santra <sayantan.santra@ou.edu>
# SPDX-License-Identifier: GPL-3.0-only
import os
import sys
import json
from packaging.version import Version
import requests as req
from bs4 import BeautifulSoup as bs
from ReVancedBuilder.Cleanup import clean_exit
# Determine the best version available to download
def apkpure_best_match(version, soup):
try:
vers_list = [Version(x['data-dt-version']) for x in soup.css.select(f"a[data-dt-apkid^=\"b/APK/\"]")]
except:
clean_exit(f" There was some error getting list of versions of {apk}...", appstate)
if version != '0':
vers_list = filter(lambda x: x <= Version(version), vers_list)
return str(max(vers_list))
# Download an apk from APKPure.com
def apkpure_dl(apk, appname, version, hard_version, session, present_vers, flag):
res = session.get(f"https://apkpure.com/{appname}/{apk}/versions")
res.raise_for_status()
soup = bs(res.text, 'html.parser')
if not hard_version:
version = apkpure_best_match(version, soup)
if flag == 'checkonly' and present_vers[apk] != version:
print(f"{apk} has an update ({present_vers[apk]} -> {version})")
return
try:
if present_vers[apk] == version and flag != 'force' and os.path.isfile(apk+'.apk'):
print(f"Recommended version {version} of {apk} is already present.")
return
except KeyError:
pass
print(f" Downloading {apk} version {version}...")
# Get the version code
try:
ver_code = soup.css.select(f"a[data-dt-version=\"{version}\"][data-dt-apkid^=\"b/APK/\"]")[0]['data-dt-versioncode']
except:
clean_exit(f" There was some error while downloading {apk}...", appstate)
res = session.get(f"https://d.apkpure.com/b/APK/{apk}?versionCode={ver_code}", stream=True)
res.raise_for_status()
with open(apk+'.apk', 'wb') as f:
for chunk in res.iter_content(chunk_size=8192):
f.write(chunk)
print(" Done!")
# Download apk files, if needed
def get_apks(appstate):
present_vers = appstate['present_vers']
build_config = appstate['build_config']
flag=appstate['flag']
print('Downloading required apk files from APKPure...')
# Get latest patches using the ReVanced API
try:
patches = req.get('https://releases.revanced.app/patches').json()
except req.exceptions.RequestException as e:
clean_exit(e, appstate)
session = req.Session()
session.headers.update({'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/116.0'})
for app in build_config:
# Check if we need to build an app
if not build_config[app].getboolean('build'):
continue
try:
apk = build_config[app]['apk']
pretty_name = build_config[app]['pretty_name']
apkpure_appname = build_config[app]['apkpure_appname']
except:
clean_exit(f"Invalid config for {app} in build_config!", appstate)
print(f"Checking {pretty_name}...")
try:
required_ver = build_config[app]['version']
required_ver[0]
hard_version = True
print(f"Using version {required_ver} of {app} from ")
except:
hard_version = False
compatible_vers = []
for patch in patches:
for pkg in patch['compatiblePackages']:
if pkg['name'] == apk:
try:
compatible_vers.append(pkg['versions'][-1])
except IndexError:
pass
if not compatible_vers:
required_ver = Version('0')
else:
required_ver = min(map(lambda x: Version(x), compatible_vers))
apkpure_dl(apk, apkpure_appname, str(required_ver), hard_version, session, present_vers, flag)
present_vers.update({apk: str(required_ver)})
appstate['present_vers'] = present_vers
return appstate

View file

@ -0,0 +1,67 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2023 Sayantan Santra <sayantan.santra@ou.edu>
# SPDX-License-Identifier: GPL-3.0-only
import os
import sys
import time
from ReVancedBuilder.Notifications import send_notif
# Move apps to proper location
def move_apps(appstate):
build_config = appstate['build_config']
print = appstate['logger'].info
try:
os.mkdir('archive')
except FileExistsError:
pass
for app in build_config:
if not build_config[app].getboolean('build'):
continue
name = build_config[app]['output_name']
final_name = f"{name}_{appstate['timestamp']}.apk"
try:
os.rename(name+'.apk', 'archive/'+final_name)
except FileNotFoundError:
pass
# sys.exit('There was an error moving the final apk files!')
# Do some cleanup, keep only the last 3 build's worth of files and a week worth of logs
with os.scandir('archive') as dir:
files = []
for f in dir:
if name in f.name:
files.append(f)
files.sort(key=lambda f: f.stat().st_ctime)
files.reverse()
for f in files[3:]:
os.remove(f)
print('Deleted old build '+f.name)
# Delete logs older than 7 days
with os.scandir('logs') as dir:
now = time.time()
for f in dir:
if f.stat().st_ctime < now - 7 * 86400:
os.remove(f)
def clean_exit(msg, appstate, code=1):
print = appstate['logger'].info
try:
appstate['notification_config']
send_notif(appstate, error=True)
except:
pass
if msg:
print(msg)
# Delete the lockfile
os.remove('lockfile')
sys.exit(code)

View file

@ -0,0 +1,91 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2023 Sayantan Santra <sayantan.santra@ou.edu>
# SPDX-License-Identifier: GPL-3.0-only
import os
import sys
import configparser as cp
import json
import subprocess
from ReVancedBuilder.Cleanup import clean_exit
# Build the revanced apps
def build_apps(appstate):
build_config = appstate['build_config']
flag = appstate['flag']
print = appstate['logger'].info
chosen_patches = cp.ConfigParser()
chosen_patches.read('chosen_patches')
try:
included_patches = json.loads(chosen_patches['patches']['included'])
except:
included_patches = []
try:
excluded_patches = json.loads(chosen_patches['patches']['excluded'])
except Exception as e:
excluded_patches = []
for app in build_config:
# Check if we need to build an app
if not build_config[app].getboolean('build'):
continue
# Build the command to be run
cmd = 'java -jar revanced-cli.jar -m revanced-integrations.apk -b revanced-patches.jar'
try:
root = build_config[app].getboolean('root')
except:
root = False
if root:
cmd += ' --mount -e microg-support'
for item in included_patches:
cmd += f" -i {item}"
for item in excluded_patches:
cmd += f" -e {item}"
if flag == 'experimental':
cmd += ' --experimental'
try:
keystore = build_config[app]['keystore']
if not root:
cmd += f" --keystore {keystore}"
except:
pass
try:
apk = build_config[app]['apk']
pretty_name = build_config[app]['pretty_name']
apkpure_appname = build_config[app]['apkpure_appname']
output_name = build_config[app]['output_name']
except:
clean_exit(f"Invalid config for {app} in build_config!", appstate)
cmd += f" -a {apk}.apk -o {output_name}.apk"
if root:
print(f"Building {pretty_name} (root)...")
else:
print(f"Building {pretty_name} (nonroot)...")
try:
with subprocess.Popen(cmd, shell=True, bufsize=0, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout as output:
for line in output:
line_utf = line.decode('utf-8').strip('\n')
if line_utf:
print(line_utf)
except Exception as e:
clean_exit(f"There was an error while building {pretty_name}!\n{e}", appstate)
try:
os.rename(output_name+'.apk', output_name+'.apk')
except FileNotFoundError:
clean_exit(f"There was an error while building {pretty_name}!", appstate)

View file

@ -0,0 +1,96 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2023 Sayantan Santra <sayantan.santra@ou.edu>
# SPDX-License-Identifier: GPL-3.0-only
import json
import re
import requests as req
import subprocess
def send_notif(appstate, error=False):
print = appstate['logger'].info
timestamp = appstate['timestamp']
if error:
msg = f"There was an error during build! Please check the logs.\nTimestamp: {timestamp}"
else:
notification_config = appstate['notification_config']
build_config = appstate['build_config']
present_vers = appstate['present_vers']
flag = appstate['flag']
msg = json.dumps(present_vers, indent=0)
msg = re.sub('("|\{|\}|,)', '', msg).strip('\n')
msg = msg.replace('revanced-', 'ReVanced ')
msg = msg.replace('cli', 'CLI')
msg = msg.replace('integrations', 'Integrations')
msg = msg.replace('patches', 'Patches')
msg = msg.replace('VancedMicroG', 'Vanced microG')
for app in build_config:
if not build_config[app].getboolean('build'):
continue
msg = msg.replace(build_config[app]['apk'], build_config[app]['pretty_name'])
msg += '\nTimestamp: ' + timestamp
if appstate['microg_updated']:
msg += '\nVanced microG was updated.'
config = appstate['notification_config']
for entry in config:
if not config[entry].getboolean('enabled'):
continue
encoded_title = '⚙⚙⚙ ReVanced Build ⚙⚙⚙'.encode('utf-8')
match entry:
case 'ntfy':
print('Sending notification through ntfy.sh...')
try:
url = config[entry]['url']
topic = config[entry]['topic']
except:
print('URL or TOPIC not provided!')
continue
headers = {'Icon': 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/40/Revanced-logo-round.svg/240px-Revanced-logo-round.svg.png',
'Title': encoded_title}
try:
req.post(f"{url}/{topic}", msg, headers=headers)
except Exception as e:
print('Failed!' + str(e))
case 'gotify':
print('Sending notification through Gotify...')
try:
url = config[entry]['url']
token = config[entry]['token']
except:
print('URL or TOKEN not provided!')
continue
data = {'Title': encoded_title, 'message': msg, 'priority': '5'}
try:
req.post(f"{url}/message?token={token}", data)
except Exception as e:
print('Failed!' + str(e))
case 'telegram':
print('Sending notification through Telegram...')
try:
chat = config[entry]['chat']
token = config[entry]['token']
except:
print('CHAT or TOKEN not provided!')
continue
cmd = f"./telegram.sh -t {token} -c {chat} -T {encoded_title} -M \"{msg}\""
try:
with subprocess.Popen(cmd, shell=True, bufsize=0, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout as output:
for line in output:
line_utf = line.decode('utf-8').strip('\n')
if line_utf:
print(line_utf)
except Exception as e:
clean_exit(f"Failed!\n{e}", appstate)
case _:
print('Don\'t know how to send notifications to ' + entry)

View file

@ -0,0 +1,191 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2023 Sayantan Santra <sayantan.santra@ou.edu>
# SPDX-License-Identifier: GPL-3.0-only
import sys
import os
import configparser as cp
import json
import logging
import subprocess
import requests as req
from packaging.version import Version
from datetime import datetime
from ReVancedBuilder.APKPure_dl import apkpure_best_match, apkpure_dl, get_apks
from ReVancedBuilder.JAVABuilder import build_apps
from ReVancedBuilder.Notifications import send_notif
from ReVancedBuilder.Cleanup import move_apps, clean_exit
# Update the ReVanced tools, if needed
def update_tools(appstate):
for item in ['revanced-cli', 'revanced-integrations', 'revanced-patches']:
*_, tool = filter(lambda x: x['repository'] == 'revanced/'+item, tools) # Get the last result
latest_ver = Version(tool['version'])
try:
present_ver = Version(appstate['present_vers'][item])
except KeyError:
present_ver = Version('0')
output_file = item+os.path.splitext(tool['name'])[1]
if flag == 'force' or not os.path.isfile(output_file) or present_ver < latest_ver:
appstate['up-to-date'] = False
print(f"{item} has an update ({str(present_ver)} -> {str(latest_ver)})")
if flag != 'checkonly':
print(f"Downloading {output_file}...")
res = req.get(tool['browser_download_url'], stream=True)
res.raise_for_status()
with open(output_file, 'wb') as f:
for chunk in res.iter_content(chunk_size=8192):
f.write(chunk)
appstate['present_vers'].update({item: str(latest_ver)})
print("Done!")
return appstate
# Update microG, if needed
def update_microg(appstate):
try:
data = req.get('https://api.github.com/repos/inotia00/VancedMicroG/releases/latest').json()['tag_name']
latest_ver = Version(data)
except req.exceptions.RequestException as e:
clean_exit(e, appstate)
try:
present_ver = Version(appstate['present_vers']['VancedMicroG'])
except KeyError:
present_ver = Version('0')
if flag == 'force' or not os.path.isfile('microg.apk') or present_ver < latest_ver:
appstate['up-to-date'] = False
print(f"Vanced microG has an update ({str(present_ver)} -> {str(latest_ver)})")
if flag != 'checkonly':
print(f"Downloading vanced-microg.apk...")
res = req.get('https://github.com/inotia00/VancedMicroG/releases/latest/download/microg.apk', stream=True)
res.raise_for_status()
with open('microg.apk', 'wb') as f:
for chunk in res.iter_content(chunk_size=8192):
f.write(chunk)
appstate['present_vers'].update({'VancedMicroG': str(latest_ver)})
print("Done!")
appstate['microg_updated'] = True
return appstate
# ------------------------------
# The main function starts here
# ------------------------------
# Create a dict for storing important data
appstate = {}
# Get a timestamp
time = datetime.now()
appstate['timestamp'] = time.strftime('%Y%m%d%H%M%S')
# Read arguments
try:
os.chdir(sys.argv[1])
except IndexError:
sys.exit('Please provide a working directory as argument!')
except FileNotFoundError:
sys.exit('Invalid working directory provided!')
# Try to make sure only one instance is running in a given working directory
try:
if os.path.exists('lockfile'):
raise FileExistsError
with open('tmplockfile', 'x') as f:
f.flush()
os.fsync(f.fileno())
os.replace('tmplockfile', 'lockfile')
except FileExistsError:
sys.exit('Another instance is already running in the same working directory!')
# Set up logging
try:
os.mkdir('logs')
except FileExistsError:
pass
logging.basicConfig(level=logging.INFO, format='%(message)s')
logger = logging.getLogger()
logger.addHandler(logging.FileHandler(f"logs/{appstate['timestamp']}.log", 'w'))
print = logger.info
appstate['logger'] = logger
# Get the flag
try:
flag = sys.argv[2]
except:
flag = None
if flag not in ['buildonly', 'checkonly', 'force', 'experimental', None]:
clean_exit(f"Unknown flag: {flag}", appstate)
appstate['flag'] = flag
appstate['microg_updated'] = False
print(f"Started building ReVanced apps at {time.strftime('%d %B, %Y %H:%M:%S')}")
print('----------------------------------------------------------------------')
# Read configs
try:
appstate['build_config']=cp.ConfigParser()
appstate['build_config'].read_file(open('build_config', 'r'))
except FileNotFoundError:
clean_exit('No build config provided, exiting. Please look at the GitHub page for more information:\n https://github.com/SinTan1729/ReVancedBuilder', appstate)
appstate['notification_config'] = cp.ConfigParser()
appstate['notification_config'].read('notification_config')
# Pull the latest information using the ReVanced API
try:
tools = req.get('https://releases.revanced.app/tools').json()['tools']
except req.exceptions.RequestException as e:
clean_exit(e, appstate)
try:
with open('versions.json', 'r') as f:
appstate['present_vers'] = json.load(f)
except:
# We'll treat empty as 0 later
appstate['present_vers'] = json.loads('{}')
appstate['up-to-date'] = True
# send_notif(appstate, error=False) # <,,,,,,,,<,,,,,,,,,,,,,
if flag != 'buildonly':
appstate = update_tools(appstate)
appstate = update_microg(appstate)
if not appstate['up-to-date'] or flag == 'force':
appstate = get_apks(appstate)
if (flag != 'checkonly' and not appstate['up-to-date']) or flag in ['force', 'buildonly']:
build_apps(appstate)
move_apps(appstate)
# Update version numbers in the versions.json file
if appstate['up-to-date'] and flag != 'buildonly':
print('There\'s nothing to do.')
elif flag != ['checkonly']:
send_notif(appstate)
try:
os.rename('versions.json', 'versions-old.json')
except FileNotFoundError:
pass
if flag != 'buildonly':
with open('versions.json', 'w') as f:
json.dump(appstate['present_vers'], f, indent=4)
try:
subprocess.run(f"{appstate['build_config']['post_script']['file']} {timestamp}", shell=True)
except:
pass
# Delete the lockfile
os.remove('lockfile')
sys.exit(0)