|
#!/data/data/com.termux/files/usr/bin/bash |
|
# |
|
# revancify-cli: CLI wrapper for Revancify |
|
# Usage: revancify-cli [options] [config_file] |
|
# If no config_file is provided, it will use ./apps.json as default |
|
|
|
# Default values |
|
DEFAULT_CONFIG="./apps.json" |
|
CONFIG_FILE="${DEFAULT_CONFIG}" |
|
VERBOSE=false |
|
DEBUG=false |
|
DEBUG_CAPTURE=false |
|
VERBOSE_DEBUG=false |
|
ROOT_MODE=false |
|
USE_SHIZUKU=true |
|
TEMP_DIR="$HOME/.revancify-cli-tmp" |
|
|
|
# ANSI colors |
|
RED='\033[0;31m' |
|
GREEN='\033[0;32m' |
|
YELLOW='\033[0;33m' |
|
BLUE='\033[0;34m' |
|
NC='\033[0m' # No Color |
|
|
|
# Print usage information |
|
usage() { |
|
echo "revancify-cli - CLI interface for Revancify" |
|
echo "" |
|
echo "Usage: revancify-cli [options] [config_file]" |
|
echo "" |
|
echo "Options:" |
|
echo " -h, --help Show this help message" |
|
echo " -v, --verbose Enable verbose output" |
|
echo " -d, --debug Enable debug mode (more verbose than -v)" |
|
echo " -X, --verbose-debug Enable extreme debugging (very verbose)" |
|
echo " -x, --debug-capture Capture UI for troubleshooting" |
|
echo " -s, --no-shizuku Don't use Shizuku for installation" |
|
echo " -r, --root Use root installation mode" |
|
echo " -c, --create-config Create a template config file" |
|
echo "" |
|
echo "Examples:" |
|
echo " revancify-cli # Use default ./apps.json" |
|
echo " revancify-cli my-apps.json # Use custom config file" |
|
echo " revancify-cli -c # Create template config" |
|
echo " revancify-cli -v my-apps.json # Verbose mode with custom config" |
|
echo " revancify-cli -r # Use root mode" |
|
echo " revancify-cli -d # Debug mode for troubleshooting" |
|
echo " revancify-cli -x # Capture UI for troubleshooting" |
|
echo " revancify-cli -X # Extreme debugging mode" |
|
echo "" |
|
} |
|
|
|
# Log messages |
|
log() { |
|
local level="$1" |
|
local message="$2" |
|
local color="$NC" |
|
|
|
case "$level" in |
|
"info") color="$BLUE" ;; |
|
"success") color="$GREEN" ;; |
|
"warning") color="$YELLOW" ;; |
|
"error") color="$RED" ;; |
|
"debug") |
|
color="$YELLOW" |
|
# Only show debug messages in debug mode |
|
if [ "$DEBUG" != true ]; then |
|
return |
|
fi |
|
;; |
|
esac |
|
|
|
echo -e "${color}[${level^^}]${NC} $message" |
|
} |
|
|
|
# Verbose logging |
|
vlog() { |
|
if [ "$VERBOSE" = true ] || [ "$DEBUG" = true ]; then |
|
log "info" "$1" |
|
fi |
|
} |
|
|
|
# Debug logging |
|
dlog() { |
|
if [ "$DEBUG" = true ]; then |
|
log "debug" "$1" |
|
fi |
|
} |
|
|
|
# Create a template config file |
|
create_template_config() { |
|
local template='{ |
|
"apps": [ |
|
{ |
|
"name": "youtube", |
|
"displayName": "YouTube", |
|
"version": "recommended", |
|
"versionType": "stable", |
|
"patchSource": "revanced-extended", |
|
"patches": ["recommended"] |
|
}, |
|
{ |
|
"name": "youtube_music", |
|
"displayName": "YouTube Music", |
|
"version": "latest", |
|
"versionType": "any", |
|
"patchSource": "revanced", |
|
"patches": ["selected", "+background-play", "+minimized-playback-music"] |
|
}, |
|
{ |
|
"name": "twitter", |
|
"displayName": "Twitter", |
|
"version": "17.45.0", |
|
"versionType": "stable", |
|
"patches": ["last"] |
|
} |
|
], |
|
"options": { |
|
"patchSource": "revanced-extended", |
|
"installAfterPatch": true, |
|
"uninstallBeforeInstall": false, |
|
"useShizuku": true, |
|
"root": false |
|
} |
|
}' |
|
|
|
local readme='' |
|
readme+="# Revancify CLI Config\n\n" |
|
readme+="## App Configuration\n\n" |
|
readme+="- **name**: Internal name of the app (used to select it in Revancify menu)\n" |
|
readme+="- **displayName**: Human-readable name for logs and messages\n" |
|
readme+="- **version**: App version to use. Options:\n" |
|
readme+=" - \"recommended\": Use Revancify's recommended version\n" |
|
readme+=" - \"latest\": Use the latest version\n" |
|
readme+=" - Specific version (e.g. \"17.45.0\"): Try to use this specific version\n" |
|
readme+="- **patchSource**: Which patch source to use. Common options:\n" |
|
readme+=" - \"revanced\": ReVanced patches\n" |
|
readme+=" - \"revanced-extended\": ReVanced Extended patches\n" |
|
readme+=" - Any other patch source name shown in Revancify\n" |
|
readme+="- **patches**: Which patches to apply. Options:\n" |
|
readme+=" - [\"recommended\"]: Use Revancify's recommended patches\n" |
|
readme+=" - [\"all\"]: Apply all available patches\n" |
|
readme+=" - [\"last\"]: Reuse your last selection\n" |
|
readme+=" - [\"selected\"]: Interactive patch selection (script will pause for user input)\n" |
|
readme+=" - Add specific patches with prefix: [\"+patch-name1\", \"+patch-name2\"]\n" |
|
readme+=" - Exclude specific patches with prefix: [\"-patch-name1\", \"-patch-name2\"]\n" |
|
readme+=" - Combine approaches: [\"recommended\", \"+additional-patch\", \"-excluded-patch\"]\n\n" |
|
readme+="## Global Options\n\n" |
|
readme+="- **patchSource**: Default patch source for all apps (unless overridden)\n" |
|
readme+="- **installAfterPatch**: Whether to install after patching\n" |
|
readme+="- **uninstallBeforeInstall**: Whether to uninstall before installing\n" |
|
readme+="- **useShizuku**: Whether to use Shizuku for installation\n" |
|
readme+="- **root**: Whether to use root for installation\n" |
|
|
|
if [ -f "./apps.json" ]; then |
|
read -p "File ./apps.json already exists. Overwrite? (y/N): " confirm |
|
if [[ $confirm != [yY] ]]; then |
|
log "warning" "Operation cancelled." |
|
exit 0 |
|
fi |
|
fi |
|
|
|
echo "$template" >"./apps.json" |
|
echo -e "$readme" >"./apps-readme.md" |
|
log "success" "Template config file created at ./apps.json" |
|
log "success" "Documentation created at ./apps-readme.md" |
|
echo "Edit these files to add your apps and configuration." |
|
exit 0 |
|
} |
|
|
|
# Check dependencies |
|
check_dependencies() { |
|
# Check for revancify |
|
if ! command -v revancify &>/dev/null; then |
|
log "error" "revancify is not installed." |
|
echo "Please install revancify first with:" |
|
echo "curl -sL https://github.com/decipher3114/Revancify/raw/refs/heads/main/install.sh | bash" |
|
exit 1 |
|
fi |
|
|
|
# Check for jq (needed to parse JSON config) |
|
if ! command -v jq &>/dev/null; then |
|
log "warning" "jq is not installed. Installing..." |
|
pkg install jq -y |
|
|
|
# Check if jq was installed successfully |
|
if ! command -v jq &>/dev/null; then |
|
log "error" "Failed to install jq. Please install it manually with 'pkg install jq -y'" |
|
exit 1 |
|
fi |
|
fi |
|
|
|
# Check for expect (needed for automation) |
|
if ! command -v expect &>/dev/null; then |
|
log "warning" "expect is not installed. Installing..." |
|
pkg install expect -y |
|
|
|
# Check if expect was installed successfully |
|
if ! command -v expect &>/dev/null; then |
|
log "error" "Failed to install expect. Please install it manually with 'pkg install expect -y'" |
|
exit 1 |
|
fi |
|
else |
|
# Verify expect works by running a simple command |
|
if ! expect -c "puts \"expect works\"" | grep -q "expect works"; then |
|
log "error" "expect is installed but not functioning properly." |
|
log "info" "Attempting to reinstall expect..." |
|
pkg reinstall expect -y |
|
|
|
# Check again |
|
if ! expect -c "puts \"expect works\"" | grep -q "expect works"; then |
|
log "error" "expect still not working properly. Please try manually removing and reinstalling it." |
|
echo "pkg remove expect -y && pkg install expect -y" |
|
exit 1 |
|
fi |
|
fi |
|
fi |
|
|
|
# Check for timeout command (needed for debug capture) |
|
if ! command -v timeout &>/dev/null; then |
|
log "warning" "timeout command is not installed. Installing coreutils..." |
|
pkg install coreutils -y |
|
|
|
# Check if timeout was installed successfully |
|
if ! command -v timeout &>/dev/null; then |
|
log "warning" "Failed to install timeout command. Debug capture may hang." |
|
fi |
|
fi |
|
|
|
log "success" "All dependencies are installed" |
|
} |
|
|
|
# Check if config file exists |
|
check_config() { |
|
# Check if explicitly provided config exists |
|
if [ "$CONFIG_FILE" != "$DEFAULT_CONFIG" ] && [ ! -f "$CONFIG_FILE" ]; then |
|
log "error" "Specified config file not found: $CONFIG_FILE" |
|
echo "Use -c or --create-config to create a template config file." |
|
exit 1 |
|
fi |
|
|
|
# For default config, check in current directory and home directory |
|
if [ "$CONFIG_FILE" = "$DEFAULT_CONFIG" ]; then |
|
if [ -f "$CONFIG_FILE" ]; then |
|
log "info" "Using config file in current directory: $CONFIG_FILE" |
|
elif [ -f "$HOME/apps.json" ]; then |
|
CONFIG_FILE="$HOME/apps.json" |
|
log "info" "Using config file from home directory: $CONFIG_FILE" |
|
else |
|
log "error" "Default config file not found: $CONFIG_FILE or $HOME/apps.json" |
|
echo "Use -c or --create-config to create a template config file." |
|
exit 1 |
|
fi |
|
fi |
|
|
|
# Validate the config file is valid JSON |
|
if ! jq '.' "$CONFIG_FILE" &>/dev/null; then |
|
log "error" "Config file is not valid JSON: $CONFIG_FILE" |
|
exit 1 |
|
fi |
|
|
|
# Validate the config file has the expected structure |
|
if ! jq '.apps' "$CONFIG_FILE" &>/dev/null; then |
|
log "error" "Config file is missing 'apps' array: $CONFIG_FILE" |
|
echo "Use -c or --create-config to create a valid template config file." |
|
exit 1 |
|
fi |
|
} |
|
|
|
# Create and manage temp directory |
|
setup_temp_dir() { |
|
# Create temp directory if it doesn't exist |
|
if [ ! -d "$TEMP_DIR" ]; then |
|
log "info" "Creating temporary directory at $TEMP_DIR" |
|
if ! mkdir -p "$TEMP_DIR"; then |
|
log "error" "Failed to create temporary directory at $TEMP_DIR" |
|
# Try an alternative location |
|
TEMP_DIR="/data/data/com.termux/files/usr/tmp/revancify-cli-tmp" |
|
log "info" "Trying alternative location: $TEMP_DIR" |
|
if ! mkdir -p "$TEMP_DIR"; then |
|
log "error" "Failed to create temporary directory at alternate location. Exiting." |
|
exit 1 |
|
fi |
|
fi |
|
else |
|
# Clean up any leftover files |
|
log "info" "Cleaning temporary directory at $TEMP_DIR" |
|
if ! rm -rf "${TEMP_DIR:?}"/*; then |
|
log "warning" "Failed to clean temporary directory. Proceeding anyway." |
|
fi |
|
fi |
|
|
|
# Make sure the directory is writable |
|
if [ ! -w "$TEMP_DIR" ]; then |
|
log "error" "Temporary directory is not writable: $TEMP_DIR" |
|
exit 1 |
|
fi |
|
|
|
log "success" "Temporary directory ready: $TEMP_DIR" |
|
} |
|
|
|
# Check if device is rooted |
|
check_root() { |
|
if [ "$ROOT_MODE" = true ]; then |
|
if ! command -v su &>/dev/null || ! su -c 'exit' &>/dev/null; then |
|
log "error" "Root access is not available. Cannot use root mode." |
|
exit 1 |
|
fi |
|
log "info" "Root access confirmed. Using root mode." |
|
fi |
|
} |
|
|
|
# Process command line arguments |
|
process_args() { |
|
# Process all arguments |
|
for ((i = 1; i <= $#; i++)); do |
|
ARG="${!i}" |
|
|
|
case "$ARG" in |
|
-h | --help) |
|
usage |
|
exit 0 |
|
;; |
|
-v | --verbose) |
|
VERBOSE=true |
|
;; |
|
-d | --debug) |
|
DEBUG=true |
|
VERBOSE=true # Debug implies verbose |
|
;; |
|
-X | --verbose-debug) |
|
VERBOSE_DEBUG=true |
|
DEBUG=true |
|
VERBOSE=true # Verbose debug implies debug and verbose |
|
;; |
|
-x | --debug-capture) |
|
DEBUG_CAPTURE=true |
|
;; |
|
-s | --no-shizuku) |
|
USE_SHIZUKU=false |
|
;; |
|
-c | --create-config) |
|
create_template_config |
|
# This function calls exit |
|
;; |
|
-r | --root) |
|
ROOT_MODE=true |
|
;; |
|
-*) |
|
log "error" "Unknown option: $ARG" |
|
usage |
|
exit 1 |
|
;; |
|
*) |
|
# This is a config file |
|
CONFIG_FILE="$ARG" |
|
;; |
|
esac |
|
done |
|
|
|
if [ "$VERBOSE" = true ]; then |
|
log "info" "Verbose mode enabled" |
|
log "info" "Using config file: $CONFIG_FILE" |
|
if [ "$USE_SHIZUKU" = true ]; then |
|
log "info" "Shizuku installation enabled" |
|
else |
|
log "info" "Shizuku installation disabled" |
|
fi |
|
if [ "$ROOT_MODE" = true ]; then |
|
log "info" "Root mode enabled" |
|
fi |
|
fi |
|
|
|
if [ "$DEBUG" = true ]; then |
|
log "debug" "Debug mode enabled" |
|
log "debug" "Environment details:" |
|
log "debug" "- Bash version: $BASH_VERSION" |
|
log "debug" "- Working directory: $(pwd)" |
|
log "debug" "- User home: $HOME" |
|
log "debug" "- Temp directory: $TEMP_DIR" |
|
fi |
|
|
|
if [ "$VERBOSE_DEBUG" = true ]; then |
|
log "debug" "Verbose debug mode enabled - printing environment" |
|
# Print all environment variables for debugging |
|
env | sort |
|
|
|
# Print all shell variables |
|
log "debug" "Shell variables:" |
|
set | grep -v "^BASH_EXECUTION_STRING=" | grep -v "^BASH_SOURCE=" | sort |
|
|
|
# Print current shell and process info |
|
log "debug" "Shell and process info:" |
|
ps -p $$ -o pid,ppid,cmd,args |
|
fi |
|
|
|
if [ "$DEBUG_CAPTURE" = true ]; then |
|
log "info" "Debug capture mode enabled" |
|
# Enable verbose mode as well for better output |
|
VERBOSE=true |
|
fi |
|
} |
|
|
|
# Create common expect script for patch source selection |
|
create_source_selection_script() { |
|
local script_file="$1" |
|
local source="$2" |
|
local purpose="$3" # For debug logs |
|
|
|
dlog "Creating source selection script for '$source' in $script_file ($purpose)" |
|
|
|
# Create basic expect script structure |
|
cat >"$script_file" <<EOF |
|
#!/usr/bin/env expect |
|
set timeout 300 |
|
EOF |
|
|
|
# Add debug logging for expect script |
|
if [ "$DEBUG" = true ]; then |
|
cat >>"$script_file" <<EOF |
|
exp_internal 1 |
|
EOF |
|
fi |
|
|
|
# Start revancify command |
|
cat >>"$script_file" <<EOF |
|
spawn revancify |
|
EOF |
|
|
|
# Add verbose logging if needed |
|
if [ "$VERBOSE" = true ] || [ "$DEBUG" = true ]; then |
|
echo "log_user 1" >>"$script_file" |
|
else |
|
echo "log_user 0" >>"$script_file" |
|
fi |
|
|
|
# Navigate to Sources menu |
|
cat >>"$script_file" <<EOF |
|
# Navigate to Sources menu |
|
expect { |
|
"*Main Menu*" { |
|
send "3\r" |
|
} |
|
timeout { |
|
puts "Timeout waiting for Main Menu" |
|
exit 1 |
|
} |
|
} |
|
|
|
# Wait for Sources menu to appear |
|
expect { |
|
"*Sources*" {} |
|
timeout { |
|
puts "Timeout waiting for Source menu" |
|
exit 1 |
|
} |
|
} |
|
EOF |
|
|
|
# Build source selection logic |
|
cat >>"$script_file" <<EOF |
|
# Try multiple approaches to select the desired source: $source |
|
# First try the first letter |
|
send "[string tolower [string index "$source" 0]]\r" |
|
|
|
# Check if that worked |
|
expect { |
|
"*Done*" { |
|
# We found a menu option and selected it |
|
puts "Source selected using first letter" |
|
send "\r" |
|
} |
|
"*Invalid*" - |
|
"*No such*" - |
|
"*Sources*" { |
|
# Try the full name lowercase |
|
send "[string tolower "$source"]\r" |
|
|
|
expect { |
|
"*Done*" { |
|
# Full name worked |
|
puts "Source selected using full name" |
|
send "\r" |
|
} |
|
"*Invalid*" - |
|
"*No such*" - |
|
"*Sources*" { |
|
# Try pressing 1, 2, 3 etc. to select from the list |
|
puts "Trying numeric selection" |
|
# Try numbers 1-5 for common sources |
|
foreach num {1 2 3 4 5} { |
|
send "\${num}\r" |
|
expect { |
|
"*Done*" { |
|
puts "Selected source using number \${num}" |
|
send "\r" |
|
break |
|
} |
|
"*Invalid*" - |
|
"*No such*" - |
|
"*Sources*" { |
|
# Continue to next number |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
# Make sure we get back to Main Menu |
|
expect { |
|
"*Main Menu*" {} |
|
timeout { |
|
# Try once more to get back |
|
send "\r" |
|
expect { |
|
"*Main Menu*" {} |
|
timeout { |
|
puts "Failed to return to Main Menu" |
|
exit 1 |
|
} |
|
} |
|
} |
|
} |
|
EOF |
|
|
|
# Complete with update assets |
|
cat >>"$script_file" <<EOF |
|
# Update assets |
|
send "4\r" |
|
expect { |
|
"*Update completed*" { send "\r" } |
|
"*Update*success*" { send "\r" } |
|
"*Main Menu*" {} |
|
timeout { |
|
puts "Timeout during update" |
|
# Try to get back to main menu |
|
send "\r" |
|
expect "*Main Menu*" |
|
} |
|
} |
|
|
|
# Make sure we're at Main Menu |
|
expect { |
|
"*Main Menu*" {} |
|
timeout { |
|
puts "Failed to return to Main Menu after update" |
|
send "\r" |
|
} |
|
} |
|
EOF |
|
|
|
# Make script executable |
|
chmod +x "$script_file" |
|
} |
|
|
|
# Simplified setup_global_patch_source using shared function |
|
setup_global_patch_source() { |
|
local source="$1" |
|
|
|
log "info" "Setting global patch source to $source" |
|
|
|
# Create temp file for the expect script |
|
local tmp_file="${TEMP_DIR}/revancify_setup.exp" |
|
|
|
# Create the expect script using shared function |
|
create_source_selection_script "$tmp_file" "$source" "global" |
|
|
|
# Add exit command to the script |
|
cat >>"$tmp_file" <<EOF |
|
# Exit |
|
send "0\r" |
|
expect eof |
|
EOF |
|
|
|
# Run the expect script |
|
"$tmp_file" |
|
local status=$? |
|
|
|
# Check if command succeeded |
|
if [ $status -eq 0 ]; then |
|
log "success" "Global patch source set to $source" |
|
else |
|
log "error" "Failed to set global patch source to $source" |
|
fi |
|
|
|
return $status |
|
} |
|
|
|
# Generate expect script for a single app |
|
generate_expect_script() { |
|
local app_json="$1" |
|
local name=$(echo "$app_json" | jq -r '.name') |
|
local display_name=$(echo "$app_json" | jq -r '.displayName') |
|
local version=$(echo "$app_json" | jq -r '.version // "recommended"') |
|
local version_type=$(echo "$app_json" | jq -r '.versionType // "any"') |
|
local patch_source=$(echo "$app_json" | jq -r '.patchSource // "default"') |
|
local patches_raw=$(echo "$app_json" | jq -r '.patches // []') |
|
|
|
# Create temp file for the expect script |
|
local tmp_file="${TEMP_DIR}/revancify_expect_${name}.exp" |
|
|
|
dlog "Generating expect script for $name at $tmp_file" |
|
|
|
# Handle custom patch source if needed |
|
if [ "$patch_source" != "default" ] && [ "$patch_source" != "$GLOBAL_PATCH_SOURCE" ]; then |
|
dlog "App $name needs to change patch source to $patch_source" |
|
|
|
# Create a separate source selection script |
|
local source_script="${TEMP_DIR}/revancify_source_${name}.exp" |
|
create_source_selection_script "$source_script" "$patch_source" "app-$name" |
|
|
|
# Add main menu return command |
|
cat >>"$source_script" <<EOF |
|
# Exit |
|
send "0\r" |
|
expect eof |
|
EOF |
|
|
|
# Run the source selection script |
|
log "info" "Changing patch source to $patch_source for $display_name" |
|
"$source_script" |
|
|
|
if [ $? -ne 0 ]; then |
|
log "error" "Failed to change patch source for $display_name" |
|
return 1 |
|
fi |
|
fi |
|
|
|
# Now create the main patching script |
|
cat >"$tmp_file" <<EOF |
|
#!/usr/bin/env expect |
|
set timeout 300 |
|
EOF |
|
|
|
# Add debug logging for expect script |
|
if [ "$DEBUG" = true ]; then |
|
cat >>"$tmp_file" <<EOF |
|
exp_internal 1 |
|
EOF |
|
fi |
|
|
|
# Start revancify command |
|
cat >>"$tmp_file" <<EOF |
|
spawn revancify |
|
EOF |
|
|
|
# Add verbose logging if needed |
|
if [ "$VERBOSE" = true ] || [ "$DEBUG" = true ]; then |
|
echo "log_user 1" >>"$tmp_file" |
|
else |
|
echo "log_user 0" >>"$tmp_file" |
|
fi |
|
|
|
# Start patching process |
|
cat >>"$tmp_file" <<EOF |
|
# Start patching |
|
expect "*Main Menu*" |
|
send "1\r" |
|
|
|
# Select app |
|
expect "*Apps Menu*" |
|
EOF |
|
|
|
# Apps menu - use simple approach to select the app |
|
cat >>"$tmp_file" <<EOF |
|
# Select app: $name |
|
# First try typing the full name |
|
send "$name\r" |
|
|
|
# Handle potential selection issues |
|
expect { |
|
"*Version Selection Menu*" { |
|
# Successfully selected the app |
|
} |
|
"*No such app*" - |
|
"*Invalid selection*" { |
|
# Try with first letter as fallback |
|
send "${name:0:1}\r" |
|
expect { |
|
"*Version Selection Menu*" { |
|
# Successfully selected the app with first letter |
|
} |
|
"*No such app*" - |
|
"*Invalid selection*" { |
|
puts "Failed to find app: $name" |
|
exit 1 |
|
} |
|
timeout { |
|
puts "Timeout while selecting app: $name" |
|
exit 1 |
|
} |
|
} |
|
} |
|
timeout { |
|
puts "Timeout while selecting app: $name" |
|
exit 1 |
|
} |
|
} |
|
EOF |
|
|
|
# Add version selection logic |
|
cat >>"$tmp_file" <<EOF |
|
# Select version |
|
expect "*Version Selection Menu*" |
|
EOF |
|
|
|
if [ "$version" = "recommended" ]; then |
|
cat >>"$tmp_file" <<EOF |
|
# Select recommended version |
|
# Try using 'r' key for recommended |
|
send "r\r" |
|
expect { |
|
"*patch menu*" { |
|
# Successfully selected recommended version |
|
} |
|
"*Invalid*" - |
|
"*No such*" { |
|
# Try searching for [RECOMMENDED] tag |
|
set found_recommended 0 |
|
|
|
# Try pressing space to see if any are marked as recommended |
|
send " " |
|
expect { |
|
"*\\\[RECOMMENDED\\\]*" { |
|
set found_recommended 1 |
|
send " \r" |
|
} |
|
default { |
|
# If not found, just select the first version |
|
send "1\r" |
|
} |
|
} |
|
} |
|
timeout { |
|
puts "Timeout while selecting recommended version" |
|
# Try the first version as fallback |
|
send "1\r" |
|
} |
|
} |
|
EOF |
|
elif [ "$version" = "latest" ]; then |
|
cat >>"$tmp_file" <<EOF |
|
# Select latest version |
|
# Try using 'a' key for auto/latest |
|
send "a\r" |
|
expect { |
|
"*patch menu*" { |
|
# Successfully selected latest version |
|
} |
|
"*Invalid*" - |
|
"*No such*" { |
|
# Try selecting the first version as it's likely the latest |
|
send "1\r" |
|
} |
|
timeout { |
|
puts "Timeout while selecting latest version" |
|
# Try the first version as fallback |
|
send "1\r" |
|
} |
|
} |
|
EOF |
|
elif [[ "$version" =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then |
|
# Specific version number format (e.g. 17.45.0) |
|
cat >>"$tmp_file" <<EOF |
|
# Try to select specific version: $version |
|
# First try typing the version |
|
send "$version\r" |
|
expect { |
|
"*patch menu*" { |
|
# Successfully selected version |
|
} |
|
"*Invalid*" - |
|
"*No such*" { |
|
# Try searching for the version by pressing keys |
|
puts "Trying to find version $version by scanning" |
|
# Try recommended as fallback |
|
send "r\r" |
|
expect "*patch menu*" |
|
} |
|
timeout { |
|
puts "Timeout while selecting version: $version" |
|
# Try recommended as fallback |
|
send "r\r" |
|
} |
|
} |
|
EOF |
|
else |
|
# Default fallback |
|
cat >>"$tmp_file" <<EOF |
|
# No specific version strategy, try auto select |
|
send "a\r" |
|
expect { |
|
"*patch menu*" { |
|
# Successfully selected version |
|
} |
|
"*Invalid*" - |
|
"*No such*" { |
|
# Try 'r' for recommended |
|
send "r\r" |
|
} |
|
timeout { |
|
puts "Timeout while selecting version" |
|
# Try '1' for first version |
|
send "1\r" |
|
} |
|
} |
|
expect "*patch menu*" |
|
EOF |
|
fi |
|
|
|
# Add patch selection logic |
|
cat >>"$tmp_file" <<EOF |
|
# Select patches |
|
expect "*patch menu*" |
|
EOF |
|
|
|
# Process patches array |
|
if echo "$patches_raw" | jq -e 'index("last")' &>/dev/null; then |
|
# Use last selected patches |
|
cat >>"$tmp_file" <<EOF |
|
# Try to use last selected patches |
|
# Try 'l' for last |
|
send "l\r" |
|
expect { |
|
"*Include/Exclude Menu*" { } |
|
"*Install Menu*" { } |
|
"*Invalid*" - |
|
"*No such*" { |
|
# Fall back to recommended patches |
|
send "r\r" |
|
expect { |
|
"*Include/Exclude Menu*" { } |
|
"*Install Menu*" { } |
|
} |
|
} |
|
} |
|
EOF |
|
elif echo "$patches_raw" | jq -e 'index("recommended")' &>/dev/null; then |
|
# Use recommended patches |
|
cat >>"$tmp_file" <<EOF |
|
# Try to use recommended patches |
|
# Try 'r' for recommended |
|
send "r\r" |
|
expect { |
|
"*Include/Exclude Menu*" { } |
|
"*Install Menu*" { } |
|
"*Invalid*" - |
|
"*No such*" { |
|
# Just try to select all patches as fallback |
|
send "a\r" |
|
expect { |
|
"*Include/Exclude Menu*" { } |
|
"*Install Menu*" { } |
|
} |
|
} |
|
} |
|
EOF |
|
elif echo "$patches_raw" | jq -e 'index("all")' &>/dev/null; then |
|
# Apply all patches |
|
cat >>"$tmp_file" <<EOF |
|
# Try to apply all patches |
|
# Try 'a' for all |
|
send "a\r" |
|
expect { |
|
"*Include/Exclude Menu*" { } |
|
"*Install Menu*" { } |
|
"*Invalid*" - |
|
"*No such*" { |
|
# Fall back to recommended patches |
|
send "r\r" |
|
expect { |
|
"*Include/Exclude Menu*" { } |
|
"*Install Menu*" { } |
|
} |
|
} |
|
} |
|
EOF |
|
elif echo "$patches_raw" | jq -e 'index("selected")' &>/dev/null; then |
|
# Interactive selection mode |
|
echo 'send "s\r"' >>"$tmp_file" |
|
echo 'interact' >>"$tmp_file" |
|
# Make script executable |
|
chmod +x "$tmp_file" |
|
# Return early as we can't automate further |
|
echo "$tmp_file" |
|
return |
|
else |
|
# Default to recommended patches |
|
cat >>"$tmp_file" <<EOF |
|
# Default to recommended patches |
|
# Try 'r' for recommended |
|
send "r\r" |
|
expect { |
|
"*Include/Exclude Menu*" { } |
|
"*Install Menu*" { } |
|
"*Invalid*" - |
|
"*No such*" { |
|
# Fallback to all patches |
|
send "a\r" |
|
expect { |
|
"*Include/Exclude Menu*" { } |
|
"*Install Menu*" { } |
|
} |
|
} |
|
} |
|
EOF |
|
fi |
|
|
|
# Handle include/exclude patches |
|
local include_patches=$(echo "$patches_raw" | jq -r '[.[] | select(startswith("+"))] | map(sub("\\\\+"; "")) | join(",")') |
|
local exclude_patches=$(echo "$patches_raw" | jq -r '[.[] | select(startswith("-"))] | map(sub("-"; "")) | join(",")') |
|
|
|
if [ -n "$include_patches" ] || [ -n "$exclude_patches" ]; then |
|
cat >>"$tmp_file" <<EOF |
|
# Include/Exclude specific patches |
|
expect "*Include/Exclude Menu*" |
|
EOF |
|
|
|
if [ -n "$include_patches" ]; then |
|
echo "send \"+$include_patches\r\"" >>"$tmp_file" |
|
fi |
|
|
|
if [ -n "$exclude_patches" ]; then |
|
echo "send \"-$exclude_patches\r\"" >>"$tmp_file" |
|
fi |
|
|
|
echo 'send "d\r"' >>"$tmp_file" # Done with include/exclude |
|
fi |
|
|
|
# Complete patching and installation |
|
cat >>"$tmp_file" <<EOF |
|
# Start patching |
|
expect "*patch menu*" |
|
send "d\r" |
|
|
|
# Wait for patching to complete |
|
expect { |
|
"*Patching completed*" { send "\r" } |
|
"*Install Menu*" { } |
|
"*Failed*" { |
|
puts "Patching failed" |
|
exit 1 |
|
} |
|
timeout { |
|
puts "Patching timed out" |
|
exit 1 |
|
} |
|
} |
|
|
|
# Installation options |
|
expect "*Install Menu*" |
|
EOF |
|
|
|
# Installation method |
|
if [ "$ROOT_MODE" = true ]; then |
|
cat >>"$tmp_file" <<EOF |
|
# Try to use root installation |
|
# Try 'r' for root |
|
send "r\r" |
|
expect { |
|
"*successfully*" { send "\r" } |
|
"*installation*" { send "\r" } |
|
"*failed*" { |
|
puts "Root installation failed, trying normal install" |
|
send "\r" |
|
# Try normal install as fallback |
|
expect "*Install Menu*" |
|
send "i\r" |
|
} |
|
timeout { |
|
puts "Installation timed out" |
|
exit 1 |
|
} |
|
} |
|
EOF |
|
elif [ "$USE_SHIZUKU" = true ]; then |
|
cat >>"$tmp_file" <<EOF |
|
# Try to use Shizuku installation |
|
# Try 's' for Shizuku |
|
send "s\r" |
|
expect { |
|
"*successfully*" { send "\r" } |
|
"*installation*" { send "\r" } |
|
"*failed*" { |
|
puts "Shizuku installation failed, trying normal install" |
|
send "\r" |
|
# Try normal install as fallback |
|
expect "*Install Menu*" |
|
send "i\r" |
|
} |
|
timeout { |
|
puts "Installation timed out" |
|
exit 1 |
|
} |
|
} |
|
EOF |
|
else |
|
cat >>"$tmp_file" <<EOF |
|
# Try to use normal installation |
|
# Try 'i' for install |
|
send "i\r" |
|
expect { |
|
"*successfully*" { send "\r" } |
|
"*installation*" { send "\r" } |
|
"*failed*" { |
|
puts "Installation failed" |
|
send "\r" |
|
} |
|
timeout { |
|
puts "Installation timed out" |
|
exit 1 |
|
} |
|
} |
|
EOF |
|
fi |
|
|
|
# Complete the script |
|
cat >>"$tmp_file" <<EOF |
|
# Return to main menu |
|
expect { |
|
"*Main Menu*" { send "0\r" } |
|
timeout { exit 1 } |
|
} |
|
|
|
# End script |
|
expect eof |
|
EOF |
|
|
|
# Make script executable |
|
chmod +x "$tmp_file" |
|
|
|
echo "$tmp_file" |
|
} |
|
|
|
# Process a single app |
|
process_app() { |
|
local app_json="$1" |
|
local name=$(echo "$app_json" | jq -r '.name') |
|
local display_name=$(echo "$app_json" | jq -r '.displayName') |
|
|
|
log "info" "Processing $display_name ($name)" |
|
|
|
# Generate the expect script for this app |
|
local expect_script=$(generate_expect_script "$app_json") |
|
|
|
# Run the expect script |
|
if [ "$VERBOSE" = true ]; then |
|
log "info" "Running expect script: $expect_script" |
|
cat "$expect_script" |
|
fi |
|
|
|
# Execute the expect script |
|
"$expect_script" |
|
local status=$? |
|
|
|
# Check if command succeeded |
|
if [ $status -eq 0 ]; then |
|
log "success" "$display_name processed successfully" |
|
else |
|
log "error" "Failed to process $display_name" |
|
fi |
|
|
|
return $status |
|
} |
|
|
|
# Process all apps in config file |
|
process_all_apps() { |
|
local config=$(cat "$CONFIG_FILE") |
|
local app_count=$(echo "$config" | jq '.apps | length') |
|
|
|
# Override settings from config file |
|
local config_use_shizuku=$(echo "$config" | jq -r '.options.useShizuku // true') |
|
if [ "$config_use_shizuku" = "false" ]; then |
|
USE_SHIZUKU=false |
|
fi |
|
|
|
# Override root mode from config if not already set |
|
if [ "$ROOT_MODE" = false ]; then |
|
local config_root_mode=$(echo "$config" | jq -r '.options.root // false') |
|
if [ "$config_root_mode" = "true" ]; then |
|
ROOT_MODE=true |
|
check_root |
|
fi |
|
fi |
|
|
|
# Get the global patch source |
|
GLOBAL_PATCH_SOURCE=$(echo "$config" | jq -r '.options.patchSource // "revanced"') |
|
|
|
log "info" "Found $app_count app(s) in config" |
|
|
|
# Check if there are any apps to process |
|
if [ "$app_count" -eq 0 ]; then |
|
log "warning" "No apps found in config file" |
|
return |
|
fi |
|
|
|
# Set up global patch source |
|
setup_global_patch_source "$GLOBAL_PATCH_SOURCE" |
|
|
|
# Process each app |
|
local success_count=0 |
|
local fail_count=0 |
|
|
|
for i in $(seq 0 $(($app_count - 1))); do |
|
local app_json=$(echo "$config" | jq ".apps[$i]") |
|
if [ -z "$app_json" ] || [ "$app_json" = "null" ]; then |
|
log "error" "Failed to extract app data for index $i" |
|
continue |
|
fi |
|
|
|
process_app "$app_json" |
|
if [ $? -eq 0 ]; then |
|
((success_count++)) |
|
else |
|
((fail_count++)) |
|
fi |
|
done |
|
|
|
log "info" "Processed $app_count app(s): $success_count succeeded, $fail_count failed" |
|
} |
|
|
|
# Create a debug capture of the revancify menu |
|
capture_revancify_menu() { |
|
log "info" "Attempting to capture revancify screens with direct commands" |
|
|
|
# Debug info - print where we are |
|
log "debug" "Current directory: $(pwd)" |
|
log "debug" "Script path: $0" |
|
log "debug" "USER: $USER, HOME: $HOME" |
|
|
|
# Check if TEMP_DIR exists |
|
if [ ! -d "$TEMP_DIR" ]; then |
|
log "error" "Temp directory does not exist: $TEMP_DIR" |
|
mkdir -p "$TEMP_DIR" || { |
|
log "error" "Failed to create temp directory" |
|
return 1 |
|
} |
|
fi |
|
|
|
# Check if we can write to TEMP_DIR |
|
if [ ! -w "$TEMP_DIR" ]; then |
|
log "error" "Cannot write to temp directory: $TEMP_DIR" |
|
return 1 |
|
fi |
|
|
|
# Check if expect is available |
|
if ! command -v expect &>/dev/null; then |
|
log "error" "expect command not found - cannot capture menus" |
|
return 1 |
|
fi |
|
|
|
# Check if revancify is available |
|
if ! command -v revancify &>/dev/null; then |
|
log "error" "revancify command not found" |
|
return 1 |
|
fi |
|
|
|
local output_dir="${TEMP_DIR}/capture" |
|
mkdir -p "$output_dir" || { |
|
log "error" "Failed to create output directory" |
|
return 1 |
|
} |
|
|
|
# Create a file to gather all output |
|
local summary_file="${output_dir}/revancify_summary.txt" |
|
|
|
# Log basic system information |
|
{ |
|
echo "=== SYSTEM INFO ===" |
|
echo "Date: $(date)" |
|
echo "OS: $(uname -a)" |
|
echo "Termux version: $(termux-info 2>/dev/null || echo "Unknown")" |
|
echo "PATH: $PATH" |
|
echo "Working directory: $(pwd)" |
|
echo "" |
|
} >"$summary_file" || { |
|
log "error" "Failed to write to summary file" |
|
return 1 |
|
} |
|
|
|
# Try to get revancify version |
|
log "info" "Checking revancify version..." |
|
{ |
|
echo "=== REVANCIFY VERSION ===" |
|
echo "Command: revancify --version" |
|
revancify --version 2>&1 || echo "Error: Unable to get version" |
|
echo "" |
|
} >>"$summary_file" || { |
|
log "error" "Failed to append to summary file" |
|
return 1 |
|
} |
|
|
|
# Try to capture help output |
|
log "info" "Capturing revancify help..." |
|
{ |
|
echo "=== REVANCIFY HELP ===" |
|
echo "Command: revancify --help" |
|
revancify --help 2>&1 || echo "Error: Unable to get help" |
|
echo "" |
|
} >>"$summary_file" || { |
|
log "error" "Failed to append to summary file" |
|
return 1 |
|
} |
|
|
|
# Create a very simple expect script to just capture the main menu |
|
local menu_script="${output_dir}/capture_menu.exp" |
|
cat >"$menu_script" <<EOF |
|
#!/usr/bin/env expect |
|
set timeout 5 |
|
log_file -noappend "${output_dir}/main_menu.txt" |
|
spawn revancify |
|
expect { |
|
"*" { |
|
# Just capture whatever shows up |
|
puts "==== CAPTURING SCREEN ====" |
|
sleep 1 |
|
# Try to exit |
|
send "0\r" |
|
} |
|
timeout { |
|
puts "Timeout waiting for any output" |
|
} |
|
} |
|
expect eof |
|
EOF |
|
chmod +x "$menu_script" |
|
|
|
# Create a simple script to try getting sources menu |
|
local sources_script="${output_dir}/capture_sources.exp" |
|
cat >"$sources_script" <<EOF |
|
#!/usr/bin/env expect |
|
set timeout 5 |
|
log_file -noappend "${output_dir}/sources_menu.txt" |
|
spawn revancify |
|
expect { |
|
"*" { |
|
puts "==== CAPTURED INITIAL SCREEN ====" |
|
# Try to go to sources menu |
|
send "3\r" |
|
sleep 1 |
|
puts "==== CAPTURING SOURCES SCREEN ====" |
|
# Try to exit |
|
send "\r0\r" |
|
} |
|
timeout { |
|
puts "Timeout waiting for any output" |
|
} |
|
} |
|
expect eof |
|
EOF |
|
chmod +x "$sources_script" |
|
|
|
# Create a simple script to try getting apps menu |
|
local apps_script="${output_dir}/capture_apps.exp" |
|
cat >"$apps_script" <<EOF |
|
#!/usr/bin/env expect |
|
set timeout 5 |
|
log_file -noappend "${output_dir}/apps_menu.txt" |
|
spawn revancify |
|
expect { |
|
"*" { |
|
puts "==== CAPTURED INITIAL SCREEN ====" |
|
# Try to go to apps menu |
|
send "1\r" |
|
sleep 1 |
|
puts "==== CAPTURING APPS SCREEN ====" |
|
# Try to exit |
|
send "\r0\r" |
|
} |
|
timeout { |
|
puts "Timeout waiting for any output" |
|
} |
|
} |
|
expect eof |
|
EOF |
|
chmod +x "$apps_script" |
|
|
|
# Try running each script with short timeout |
|
log "info" "Capturing main menu (5 sec timeout)..." |
|
timeout 5 "$menu_script" || log "warning" "Main menu capture timed out" |
|
|
|
log "info" "Capturing sources menu (5 sec timeout)..." |
|
timeout 5 "$sources_script" || log "warning" "Sources menu capture timed out" |
|
|
|
log "info" "Capturing apps menu (5 sec timeout)..." |
|
timeout 5 "$apps_script" || log "warning" "Apps menu capture timed out" |
|
|
|
# Gather all the data we captured |
|
{ |
|
echo "=== CAPTURED SCREENS ===" |
|
|
|
if [ -f "${output_dir}/main_menu.txt" ]; then |
|
echo "--- MAIN MENU ---" |
|
cat "${output_dir}/main_menu.txt" |
|
echo "" |
|
fi |
|
|
|
if [ -f "${output_dir}/sources_menu.txt" ]; then |
|
echo "--- SOURCES MENU ---" |
|
cat "${output_dir}/sources_menu.txt" |
|
echo "" |
|
fi |
|
|
|
if [ -f "${output_dir}/apps_menu.txt" ]; then |
|
echo "--- APPS MENU ---" |
|
cat "${output_dir}/apps_menu.txt" |
|
echo "" |
|
fi |
|
} >>"$summary_file" |
|
|
|
# Try to extract some useful info from the captures |
|
{ |
|
echo "=== ANALYSIS ===" |
|
|
|
# Look for sources in the sources menu |
|
if [ -f "${output_dir}/sources_menu.txt" ]; then |
|
echo "Sources found:" |
|
grep -i "revanced\|source" "${output_dir}/sources_menu.txt" | |
|
grep -v "Main Menu\|Source Menu" || echo "None detected" |
|
echo "" |
|
fi |
|
|
|
# Look for apps in the apps menu |
|
if [ -f "${output_dir}/apps_menu.txt" ]; then |
|
echo "Apps found:" |
|
grep -A 10 "Apps Menu" "${output_dir}/apps_menu.txt" | |
|
grep -v "Apps Menu\|====" || echo "None detected" |
|
echo "" |
|
fi |
|
} >>"$summary_file" |
|
|
|
# Display the summary |
|
if [ -f "$summary_file" ] && [ -s "$summary_file" ]; then |
|
log "success" "Capture completed. Here's what we found:" |
|
echo "============ MENU CAPTURE SUMMARY ============" |
|
cat "$summary_file" |
|
echo "==============================================" |
|
return 0 |
|
else |
|
log "error" "Failed to capture any revancify information" |
|
return 1 |
|
fi |
|
} |
|
|
|
# Main function |
|
main() { |
|
# Process command line arguments |
|
process_args "$@" |
|
|
|
# Set up trap for SIGINT to clean up temp directory |
|
trap cleanup SIGINT SIGTERM |
|
|
|
# Check dependencies |
|
check_dependencies |
|
|
|
# Check for root if needed |
|
if [ "$ROOT_MODE" = true ]; then |
|
check_root |
|
fi |
|
|
|
# Special handling for debug capture mode |
|
if [ "$DEBUG_CAPTURE" = true ]; then |
|
# Set up temp directory |
|
setup_temp_dir |
|
|
|
log "info" "Running debug capture with 15-second timeout..." |
|
|
|
# Check if timeout command exists |
|
if command -v timeout >/dev/null 2>&1; then |
|
# Use system timeout command directly on this script with the function |
|
timeout 15 capture_revancify_menu |
|
capture_exit=$? |
|
|
|
if [ $capture_exit -eq 124 ] || [ $capture_exit -eq 143 ]; then |
|
log "warning" "Debug capture timed out after 15 seconds" |
|
elif [ $capture_exit -ne 0 ]; then |
|
log "error" "Debug capture failed with exit code $capture_exit" |
|
fi |
|
else |
|
# Fallback to direct execution with no timeout |
|
log "warning" "timeout command not found, running without timeout protection" |
|
capture_revancify_menu |
|
capture_exit=$? |
|
fi |
|
|
|
# Always cleanup |
|
cleanup |
|
exit $capture_exit |
|
fi |
|
|
|
# Check config file |
|
check_config |
|
|
|
# Set up temp directory |
|
setup_temp_dir |
|
|
|
# Process all apps |
|
process_all_apps |
|
local status=$? |
|
|
|
# Clean up |
|
cleanup |
|
|
|
log "success" "All operations completed!" |
|
return $status |
|
} |
|
|
|
# Cleanup function |
|
cleanup() { |
|
if [ -d "$TEMP_DIR" ] && [[ "$TEMP_DIR" == *revancify-cli* ]]; then |
|
log "info" "Cleaning up temporary files" |
|
rm -rf "$TEMP_DIR" |
|
fi |
|
} |
|
|
|
# Set up for better error handling |
|
trap 'echo -e "${RED}[ERROR]${NC} Error occurred on line $LINENO. Command: $BASH_COMMAND" >&2' ERR |
|
|
|
# Run main function |
|
main "$@" |