Ttgg / app.py
Athagi's picture
Update app.py
c2c730d verified
import os
import subprocess
import zipfile
import tempfile
import shutil
import xml.etree.ElementTree as ET
import magic
import requests
from flask import Flask, request, jsonify, send_file, abort, render_template
from werkzeug.utils import secure_filename
app = Flask(__name__)
# Configuration
UPLOAD_FOLDER = 'uploads'
APKTOOL_VERSION = '2.9.1' # Latest stable version
APKTOOL_URL = f'https://github.com/iBotPeaches/Apktool/releases/download/v{APKTOOL_VERSION}/apktool_{APKTOOL_VERSION}.jar'
APKTOOL_PATH = 'apktool.jar'
KEYSTORE_PATH = 'debug.keystore'
KEYSTORE_PASS = 'android'
KEY_ALIAS = 'androiddebugkey'
# Ensure directories exist
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
def download_apktool():
"""Download Apktool if not present"""
if not os.path.exists(APKTOOL_PATH):
print(f"Downloading Apktool v{APKTOOL_VERSION}...")
try:
response = requests.get(APKTOOL_URL, stream=True)
with open(APKTOOL_PATH, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
print("Apktool downloaded successfully")
except Exception as e:
print(f"Failed to download Apktool: {str(e)}")
raise
def verify_apk(file_path):
"""Verify if file is a valid APK"""
mime = magic.Magic(mime=True)
file_type = mime.from_file(file_path)
return file_type == 'application/vnd.android.package-archive'
@app.route('/')
def index():
return render_template('index.html')
@app.route('/api/upload', methods=['POST'])
def upload_apk():
"""Upload an APK file"""
if 'file' not in request.files:
return jsonify({'error': 'No file part'}), 400
file = request.files['file']
if file.filename == '':
return jsonify({'error': 'No selected file'}), 400
if file and file.filename.endswith('.apk'):
filename = secure_filename(file.filename)
filepath = os.path.join(UPLOAD_FOLDER, filename)
file.save(filepath)
# Verify it's actually an APK
if not verify_apk(filepath):
os.remove(filepath)
return jsonify({'error': 'Invalid APK file'}), 400
return jsonify({
'success': True,
'filename': filename,
'filepath': filepath
})
else:
return jsonify({'error': 'Invalid file type. Only APK files are allowed.'}), 400
@app.route('/api/decompile', methods=['POST'])
def decompile_apk():
"""Decompile an APK using apktool"""
data = request.get_json()
if not data or 'apk_path' not in data:
return jsonify({'error': 'APK path required'}), 400
apk_path = data['apk_path']
if not os.path.exists(apk_path):
return jsonify({'error': 'APK file not found'}), 404
output_dir = apk_path.replace('.apk', '_decompiled')
try:
# Ensure apktool is available
download_apktool()
# Run apktool to decompile
result = subprocess.run(
['java', '-jar', APKTOOL_PATH, 'd', apk_path, '-o', output_dir, '-f'],
capture_output=True,
text=True
)
if result.returncode != 0:
return jsonify({
'error': 'Decompilation failed',
'details': result.stderr
}), 500
return jsonify({
'success': True,
'output_dir': output_dir
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/recompile', methods=['POST'])
def recompile_apk():
"""Recompile a decompiled APK"""
data = request.get_json()
if not data or 'decompiled_dir' not in data:
return jsonify({'error': 'Decompiled directory required'}), 400
decompiled_dir = data['decompiled_dir']
output_apk = decompiled_dir.replace('_decompiled', '_modified.apk')
try:
result = subprocess.run(
['java', '-jar', APKTOOL_PATH, 'b', decompiled_dir, '-o', output_apk],
capture_output=True,
text=True
)
if result.returncode != 0:
return jsonify({
'error': 'Recompilation failed',
'details': result.stderr
}), 500
return jsonify({
'success': True,
'output_apk': output_apk
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/sign', methods=['POST'])
def sign_apk():
"""Sign an APK file"""
data = request.get_json()
if not data or 'apk_path' not in data:
return jsonify({'error': 'APK path required'}), 400
apk_path = data['apk_path']
if not os.path.exists(apk_path):
return jsonify({'error': 'APK file not found'}), 404
signed_apk = apk_path.replace('.apk', '_signed.apk')
try:
# Sign the APK
result = subprocess.run(
['jarsigner', '-verbose', '-sigalg', 'SHA1withRSA', '-digestalg', 'SHA1',
'-keystore', KEYSTORE_PATH, '-storepass', KEYSTORE_PASS,
apk_path, KEY_ALIAS],
capture_output=True,
text=True
)
if result.returncode != 0:
return jsonify({
'error': 'Signing failed',
'details': result.stderr
}), 500
# Zipalign the APK
result = subprocess.run(
['zipalign', '-v', '4', apk_path, signed_apk],
capture_output=True,
text=True
)
if result.returncode != 0:
return jsonify({
'error': 'Zipalign failed',
'details': result.stderr
}), 500
return jsonify({
'success': True,
'signed_apk': signed_apk
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/download', methods=['GET'])
def download_apk():
"""Download a processed APK file"""
apk_path = request.args.get('apk_path')
if not apk_path:
abort(400, description="APK path parameter is required")
if not os.path.exists(apk_path):
abort(404, description="APK file not found")
if not apk_path.endswith('.apk'):
abort(400, description="Invalid file type")
return send_file(apk_path, as_attachment=True)
@app.route('/api/extract_icon', methods=['POST'])
def extract_icon():
"""Extract icon from APK"""
data = request.get_json()
if not data or 'apk_path' not in data:
return jsonify({'error': 'APK path required'}), 400
apk_path = data['apk_path']
if not os.path.exists(apk_path):
return jsonify({'error': 'APK file not found'}), 404
try:
with zipfile.ZipFile(apk_path, 'r') as zip_ref:
icon_paths = [
'res/mipmap-hdpi-v4/ic_launcher.png',
'res/mipmap-mdpi-v4/ic_launcher.png',
'res/mipmap-xhdpi-v4/ic_launcher.png',
'res/mipmap-xxhdpi-v4/ic_launcher.png',
'res/mipmap-xxxhdpi-v4/ic_launcher.png',
'res/drawable-hdpi/ic_launcher.png',
'res/drawable/ic_launcher.png'
]
icon_file = None
for path in icon_paths:
if path in zip_ref.namelist():
icon_file = path
break
if not icon_file:
return jsonify({'error': 'Icon not found in APK'}), 404
temp_dir = tempfile.mkdtemp()
extracted_path = os.path.join(temp_dir, os.path.basename(icon_file))
with zip_ref.open(icon_file) as source, open(extracted_path, 'wb') as target:
shutil.copyfileobj(source, target)
return send_file(extracted_path, mimetype='image/png')
except Exception as e:
return jsonify({'error': str(e)}), 500
finally:
if 'temp_dir' in locals():
shutil.rmtree(temp_dir, ignore_errors=True)
def create_debug_keystore():
"""Create debug keystore if it doesn't exist"""
if not os.path.exists(KEYSTORE_PATH):
print("Creating debug keystore...")
try:
subprocess.run([
'keytool', '-genkey', '-v',
'-keystore', KEYSTORE_PATH,
'-alias', KEY_ALIAS,
'-keyalg', 'RSA',
'-keysize', '2048',
'-validity', '10000',
'-storepass', KEYSTORE_PASS,
'-keypass', KEYSTORE_PASS,
'-dname', 'CN=Android Debug,O=Android,C=US'
], check=True)
print("Debug keystore created successfully")
except subprocess.CalledProcessError as e:
print(f"Failed to create debug keystore: {str(e)}")
raise
if __name__ == '__main__':
# Ensure we have all required tools
try:
download_apktool()
create_debug_keystore()
app.run(host="0.0.0.0", port=7860)
except Exception as e:
print(f"Failed to start application: {str(e)}")