import os
import sys
import json
import uuid
import time
import shutil
import base64
import threading
import subprocess
import re
from datetime import datetime
from flask import Flask, request, jsonify, send_file, Response
app = Flask(__name__)
BUILD_DIR_BASE = "/tmp/builds"
ROOT_DIR = os.getcwd()
KEYSTORE_PATH = os.path.join(ROOT_DIR, "release-key.jks")
LOG_STORE = {}
BUILD_STATUS = {}
BUILD_ARTIFACTS = {}
os.makedirs(BUILD_DIR_BASE, exist_ok=True)
def init_keystore():
if not os.path.exists(KEYSTORE_PATH):
keytool_cmd = [
"keytool", "-genkeypair",
"-v",
"-keystore", KEYSTORE_PATH,
"-keyalg", "RSA",
"-keysize", "2048",
"-validity", "10000",
"-alias", "releasekey",
"-storepass", "android123",
"-keypass", "android123",
"-dname", "CN=SkyData, OU=Mobile, O=Company, L=City, S=State, C=US"
]
subprocess.run(keytool_cmd, capture_output=True, timeout=60)
init_keystore()
def get_sha1():
if not os.path.exists(KEYSTORE_PATH):
return None
cmd = [
"keytool", "-list", "-v",
"-keystore", KEYSTORE_PATH,
"-storepass", "android123",
"-alias", "releasekey"
]
result = subprocess.run(cmd, capture_output=True, text=True)
match = re.search(r"SHA1:\s+([A-F0-9:]+)", result.stdout)
if match:
return match.group(1)
return None
def analyze_logs_ai(log_text):
diagnosis_lines = []
if "Could not resolve" in log_text or "Could not find" in log_text:
diagnosis_lines.append("خطأ في التبعيات: لم يتم العثور على مكتبة مطلوبة. تأكد من أن جميع المكتبات المحددة متوفرة في مستودعات Maven/Google.")
if "FirebaseApp" in log_text or "google-services" in log_text or "com.google.gms" in log_text:
diagnosis_lines.append("خطأ في Firebase: ملف google-services.json غير صالح أو غير متوافق مع اسم الحزمة. تحقق من إعدادات Firebase الخاصة بك.")
if "AAPT" in log_text or "resource" in log_text.lower() and "not found" in log_text.lower():
diagnosis_lines.append("خطأ في الموارد: مورد XML أو صورة مفقودة. تأكد من صحة جميع ملفات الموارد.")
if "Syntax error" in log_text or "Unexpected token" in log_text or "illegal start" in log_text.lower():
diagnosis_lines.append("خطأ في بناء الجملة: يوجد خطأ نحوي في كود Java. راجع ملفات المصدر بعناية.")
if "OutOfMemoryError" in log_text or "heap" in log_text.lower():
diagnosis_lines.append("خطأ في الذاكرة: نفدت ذاكرة JVM أثناء البناء. حاول زيادة حجم الذاكرة المخصصة.")
if "SDK location not found" in log_text or "ANDROID_HOME" in log_text:
diagnosis_lines.append("خطأ في SDK: لم يتم العثور على Android SDK. تحقق من متغيرات البيئة.")
if "Manifest merger failed" in log_text:
diagnosis_lines.append("خطأ في دمج الـ Manifest: تعارض بين إعدادات AndroidManifest.xml. راجع الأذونات والإعدادات.")
if "Execution failed for task" in log_text:
task_match = re.search(r"Execution failed for task '([^']+)'", log_text)
if task_match:
diagnosis_lines.append("فشل تنفيذ المهمة: {} - تحقق من إعدادات هذه المهمة والتبعيات المرتبطة بها.".format(task_match.group(1)))
if "minSdkVersion" in log_text:
diagnosis_lines.append("خطأ في إصدار SDK الأدنى: إصدار SDK الأدنى المحدد غير متوافق مع إحدى المكتبات.")
if "Duplicate class" in log_text:
diagnosis_lines.append("خطأ تكرار: يوجد تكرار في الفئات بين المكتبات. استخدم exclude لحل التعارضات.")
if "R8" in log_text and ("error" in log_text.lower() or "failed" in log_text.lower()):
diagnosis_lines.append("خطأ في R8/ProGuard: فشل تقليص الكود. تحقق من قواعد ProGuard وتأكد من عدم حذف فئات مطلوبة.")
if not diagnosis_lines:
if "BUILD SUCCESSFUL" in log_text:
diagnosis_lines.append("تم البناء بنجاح! لا توجد أخطاء.")
else:
diagnosis_lines.append("لم يتم التعرف على خطأ محدد. راجع سجل البناء الكامل يدوياً للبحث عن المشكلة.")
return "\n".join(diagnosis_lines)
def generate_offline_html():
return """
Offline
🔌
لا يوجد اتصال بالإنترنت
التطبيق بانتظار عودة الشبكة لإعادة الاتصال تلقائياً...
إعادة المحاولة
"""
def create_build_gradle_root(project_dir, use_firebase, use_onesignal):
firebase_classpath = ""
onesignal_classpath = ""
if use_firebase:
firebase_classpath = " classpath 'com.google.gms:google-services:4.4.0'"
if use_onesignal:
onesignal_classpath = " classpath 'gradle.plugin.com.onesignal:onesignal-gradle-plugin:0.14.0'"
content = """buildscript {{
repositories {{
google()
mavenCentral()
gradlePluginPortal()
}}
dependencies {{
classpath 'com.android.tools.build:gradle:7.4.2'
{firebase_cp}
{onesignal_cp}
}}
}}
allprojects {{
repositories {{
google()
mavenCentral()
gradlePluginPortal()
}}
}}
task clean(type: Delete) {{
delete rootProject.buildDir
}}
""".format(firebase_cp=firebase_classpath, onesignal_cp=onesignal_classpath)
with open(os.path.join(project_dir, "build.gradle"), "w") as f:
f.write(content)
def create_settings_gradle(project_dir, app_name):
content = """rootProject.name = "{app_name}"
include ':app'
""".format(app_name=app_name.replace('"', '\\"'))
with open(os.path.join(project_dir, "settings.gradle"), "w") as f:
f.write(content)
def create_gradle_properties(project_dir):
content = """org.gradle.jvmargs=-Xmx3072m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.daemon=false
org.gradle.parallel=true
org.gradle.caching=true
android.useAndroidX=true
android.enableJetifier=true
"""
with open(os.path.join(project_dir, "gradle.properties"), "w") as f:
f.write(content)
def create_gradle_wrapper(project_dir):
wrapper_dir = os.path.join(project_dir, "gradle", "wrapper")
os.makedirs(wrapper_dir, exist_ok=True)
props_content = """distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\\://services.gradle.org/distributions/gradle-7.6-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
"""
with open(os.path.join(wrapper_dir, "gradle-wrapper.properties"), "w") as f:
f.write(props_content)
def create_app_build_gradle(app_dir, config):
package_name = config.get("package_name", "com.example.webapp")
version_name = config.get("version_name", "1.0.0")
version_code = config.get("version_code", "1")
minify_enabled = "true" if config.get("proguard_enabled") else "false"
split_apk = config.get("split_apk", False)
use_firebase = config.get("actual_firebase_enabled", False)
use_onesignal = bool(config.get("onesignal_app_id", "").strip())
use_admob = config.get("admob_enabled", False)
dependencies = []
dependencies.append(" implementation 'androidx.appcompat:appcompat:1.6.1'")
dependencies.append(" implementation 'androidx.webkit:webkit:1.8.0'")
dependencies.append(" implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'")
dependencies.append(" implementation 'com.google.android.material:material:1.10.0'")
dependencies.append(" implementation 'com.google.android.gms:play-services-auth:20.7.0'")
if use_firebase:
dependencies.append(" implementation platform('com.google.firebase:firebase-bom:32.7.0')")
dependencies.append(" implementation 'com.google.firebase:firebase-analytics'")
dependencies.append(" implementation 'com.google.firebase:firebase-messaging'")
if use_onesignal:
dependencies.append(" implementation 'com.onesignal:OneSignal:4.114.0'")
if use_admob:
dependencies.append(" implementation 'com.google.android.gms:play-services-ads:22.6.0'")
deps_str = "\n".join(dependencies)
plugins_str = " id 'com.android.application'\n"
if use_firebase:
plugins_str += " id 'com.google.gms.google-services'\n"
if use_onesignal:
plugins_str += " id 'com.onesignal.androidsdk.onesignal-gradle-plugin'\n"
proguard_rules = ""
if config.get("proguard_enabled"):
proguard_rules = """ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'"""
splits_block = ""
if split_apk:
splits_block = """
splits {
abi {
enable true
reset()
include 'armeabi-v7a', 'arm64-v8a'
universalApk true
}
}"""
content = """plugins {{
{plugins}
}}
android {{
namespace '{package_name}'
compileSdk 34
defaultConfig {{
applicationId "{package_name}"
minSdk 24
targetSdk 34
versionCode {version_code}
versionName "{version_name}"
}}
signingConfigs {{
release {{
storeFile file("../release-key.jks")
storePassword "android123"
keyAlias "releasekey"
keyPassword "android123"
}}
}}
buildTypes {{
release {{
minifyEnabled {minify_enabled}
shrinkResources {minify_enabled}
{proguard_rules}
signingConfig signingConfigs.release
}}
}}
{splits}
compileOptions {{
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}}
lint {{
abortOnError false
checkReleaseBuilds false
}}
}}
dependencies {{
{deps_str}
}}
""".format(
plugins=plugins_str,
package_name=package_name,
version_code=version_code,
version_name=version_name,
minify_enabled=minify_enabled,
proguard_rules=proguard_rules,
splits=splits_block,
deps_str=deps_str
)
with open(os.path.join(app_dir, "build.gradle"), "w") as f:
f.write(content)
def create_proguard_rules(app_dir, config):
rules = """-keepattributes Signature
-keepattributes *Annotation*
-keep class * extends android.app.Activity
-keep class * extends android.app.Application
-keep class * extends android.app.Service
-keep class * extends android.content.BroadcastReceiver
-keep class * extends android.content.ContentProvider
-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn javax.annotation.**
-keep class com.google.android.gms.** { *; }
-keep class com.google.firebase.** { *; }
-keep class androidx.** { *; }
-keep class com.onesignal.** { *; }
-dontwarn com.onesignal.**
-keepclassmembers class * {
@android.webkit.JavascriptInterface ;
}
"""
with open(os.path.join(app_dir, "proguard-rules.pro"), "w") as f:
f.write(rules)
def create_android_manifest(manifest_dir, config):
package_name = config.get("package_name", "com.example.webapp")
orientation = config.get("screen_orientation", "unspecified")
if orientation == "portrait":
orientation_str = "portrait"
elif orientation == "landscape":
orientation_str = "landscape"
else:
orientation_str = "unspecified"
permissions = []
permissions.append(' ')
permissions.append(' ')
permissions.append(' ')
if config.get("perm_camera"):
permissions.append(' ')
permissions.append(' ')
if config.get("perm_microphone"):
permissions.append(' ')
if config.get("perm_location"):
permissions.append(' ')
permissions.append(' ')
if config.get("perm_storage"):
permissions.append(' ')
permissions.append(' ')
if config.get("perm_vibrate"):
permissions.append(' ')
if config.get("perm_contacts"):
permissions.append(' ')
if config.get("perm_notifications"):
permissions.append(' ')
permissions_str = "\n".join(permissions)
fullscreen_theme = ""
if config.get("fullscreen_mode"):
fullscreen_theme = 'android:theme="@style/Theme.AppCompat.NoActionBar"'
else:
fullscreen_theme = 'android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"'
splash_activity = ""
main_activity_intent = ""
if config.get("splash_enabled"):
splash_activity = """
""".format(orientation=orientation_str, fullscreen_theme_attr=fullscreen_theme)
main_activity_intent = ""
else:
main_activity_intent = """
"""
meta_data = ""
if config.get("admob_enabled"):
meta_data += """
"""
content = """
{permissions}
{splash_activity}
{main_activity_intent}
{meta_data}
""".format(
permissions=permissions_str,
app_name=config.get("app_name", "WebApp").replace('"', '\\"'),
fullscreen_theme=fullscreen_theme,
splash_activity=splash_activity,
orientation=orientation_str,
main_activity_intent=main_activity_intent,
meta_data=meta_data
)
with open(os.path.join(manifest_dir, "AndroidManifest.xml"), "w") as f:
f.write(content)
def create_main_activity(java_dir, config):
package_name = config.get("package_name", "com.example.webapp")
target_url = config.get("target_url", "https://www.google.com")
enable_js = config.get("enable_javascript", True)
enable_dom = config.get("enable_dom_storage", True)
allow_file_access = config.get("allow_file_access", False)
media_autoplay = config.get("media_autoplay", False)
enable_zoom = config.get("enable_zoom", False)
custom_ua = config.get("custom_user_agent", "")
pull_to_refresh = config.get("pull_to_refresh", False)
multi_windows = config.get("multi_windows", False)
deep_link_intercept = config.get("deep_link_intercept", True)
prevent_screenshots = config.get("prevent_screenshots", False)
clear_cache_exit = config.get("clear_cache_exit", False)
clear_cookies_exit = config.get("clear_cookies_exit", False)
keep_screen_on = config.get("keep_screen_on", False)
fullscreen_mode = config.get("fullscreen_mode", False)
enforce_https = config.get("enforce_https", True)
dev_tools_f12 = config.get("dev_tools_f12", True)
root_detection = config.get("root_detection", True)
anti_frida = config.get("anti_frida", True)
tamper_protection = config.get("tamper_protection", True)
prevent_memory_edit = config.get("prevent_memory_edit", True)
smart_cache = config.get("smart_cache", True)
status_bar_color = config.get("status_bar_color", "#1a1a2e")
bottom_nav = config.get("bottom_nav_enabled", False)
offline_mode = config.get("offline_mode", True)
mixed_content = "WebSettings.MIXED_CONTENT_NEVER_ALLOW" if enforce_https else "WebSettings.MIXED_CONTENT_ALWAYS_ALLOW"
use_admob = config.get("admob_enabled", False)
onesignal_id = config.get("onesignal_app_id", "").strip()
use_onesignal = bool(onesignal_id)
google_web_client_id = config.get("google_web_client_id", "")
supabase_url = config.get("supabase_url", "")
supabase_anon_key = config.get("supabase_anon_key", "")
imports = []
imports.append("package {};".format(package_name))
imports.append("")
imports.append("import android.app.Activity;")
imports.append("import android.content.Context;")
imports.append("import android.content.Intent;")
imports.append("import android.content.pm.PackageManager;")
imports.append("import android.graphics.Bitmap;")
imports.append("import android.graphics.Color;")
imports.append("import android.net.ConnectivityManager;")
imports.append("import android.net.Network;")
imports.append("import android.net.NetworkCapabilities;")
imports.append("import android.net.NetworkInfo;")
imports.append("import android.net.NetworkRequest;")
imports.append("import android.net.Uri;")
imports.append("import android.os.Build;")
imports.append("import android.os.Bundle;")
imports.append("import android.util.Log;")
imports.append("import android.view.View;")
imports.append("import android.view.Window;")
imports.append("import android.view.WindowManager;")
imports.append("import android.webkit.ConsoleMessage;")
imports.append("import android.webkit.CookieManager;")
imports.append("import android.webkit.GeolocationPermissions;")
imports.append("import android.webkit.JavascriptInterface;")
imports.append("import android.webkit.PermissionRequest;")
imports.append("import android.webkit.ValueCallback;")
imports.append("import android.webkit.WebChromeClient;")
imports.append("import android.webkit.WebResourceRequest;")
imports.append("import android.webkit.WebSettings;")
imports.append("import android.webkit.WebView;")
imports.append("import android.webkit.WebViewClient;")
imports.append("import android.widget.FrameLayout;")
imports.append("import android.widget.LinearLayout;")
imports.append("import android.widget.ImageButton;")
imports.append("import android.widget.ProgressBar;")
imports.append("import android.widget.Toast;")
imports.append("import androidx.appcompat.app.AppCompatActivity;")
imports.append("import androidx.core.app.ActivityCompat;")
imports.append("import androidx.core.content.ContextCompat;")
imports.append("import com.google.android.gms.auth.api.signin.GoogleSignIn;")
imports.append("import com.google.android.gms.auth.api.signin.GoogleSignInAccount;")
imports.append("import com.google.android.gms.auth.api.signin.GoogleSignInClient;")
imports.append("import com.google.android.gms.auth.api.signin.GoogleSignInOptions;")
imports.append("import com.google.android.gms.common.api.ApiException;")
imports.append("import com.google.android.gms.tasks.Task;")
if use_onesignal:
imports.append("import com.onesignal.OneSignal;")
if pull_to_refresh:
imports.append("import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;")
imports.append("import java.io.File;")
imports.append("import java.io.BufferedReader;")
imports.append("import java.io.FileReader;")
imports.append("import java.util.ArrayList;")
imports.append("import java.util.List;")
imports.append("")
root_detection_code = ""
root_detection_method = ""
if root_detection:
root_detection_method = """
private boolean isRootedOrEmulator() {
String[] rootPaths = {"/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su", "/system/bin/failsafe/su", "/data/local/su", "/su/bin/su"};
for (String path : rootPaths) {
if (new File(path).exists()) return true;
}
String buildTags = android.os.Build.TAGS;
if (buildTags != null && buildTags.contains("test-keys")) return true;
if (Build.FINGERPRINT.startsWith("generic") || Build.FINGERPRINT.startsWith("unknown") || Build.MODEL.contains("google_sdk") || Build.MODEL.contains("Emulator") || Build.MODEL.contains("Android SDK built for x86") || Build.MANUFACTURER.contains("Genymotion")) return true;
return false;
}"""
root_detection_code = """
if (isRootedOrEmulator()) {
Toast.makeText(this, "Security Alert: Device not secure.", Toast.LENGTH_LONG).show();
finish();
return;
}"""
frida_method = ""
if anti_frida:
frida_method = """
private boolean detectFridaAndXposed() {
try {
java.net.Socket socket = new java.net.Socket("127.0.0.1", 27042);
socket.close();
return true;
} catch (Exception e) { }
try {
throw new Exception("Stack trace check");
} catch (Exception e) {
for (StackTraceElement element : e.getStackTrace()) {
if (element.getClassName().contains("de.robv.android.xposed.") || element.getClassName().contains("com.saurik.substrate.")) {
return true;
}
}
}
return false;
}"""
root_detection_code += """
if (detectFridaAndXposed()) {
Toast.makeText(this, "Security Alert: Hooking framework detected.", Toast.LENGTH_LONG).show();
finish();
return;
}"""
tamper_method = ""
if tamper_protection:
tamper_method = """
private boolean verifyAppSignature() {
try {
android.content.pm.PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), android.content.pm.PackageManager.GET_SIGNATURES);
for (android.content.pm.Signature signature : pInfo.signatures) {
java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA");
md.update(signature.toByteArray());
String currentSignature = android.util.Base64.encodeToString(md.digest(), android.util.Base64.DEFAULT).trim();
if (currentSignature != null && currentSignature.length() > 0) return true;
}
} catch (Exception e) { e.printStackTrace(); }
return false;
}"""
root_detection_code += """
if (!verifyAppSignature()) {
Toast.makeText(this, "Security Alert: App integrity compromised.", Toast.LENGTH_LONG).show();
finish();
return;
}"""
memory_method = ""
if prevent_memory_edit:
memory_method = """
private boolean detectMemoryEditors() {
String[] suspiciousPackages = {"com.catchdroid.gameguardian", "org.sbtools.gamehack", "com.cih.gamecih", "com.silead.guardian"};
PackageManager pm = getPackageManager();
for (String pkg : suspiciousPackages) {
try {
pm.getPackageInfo(pkg, PackageManager.GET_ACTIVITIES);
return true;
} catch (PackageManager.NameNotFoundException e) { }
}
return false;
}"""
root_detection_code += """
if (detectMemoryEditors()) {
Toast.makeText(this, "Security Alert: Memory Editor detected.", Toast.LENGTH_LONG).show();
finish();
return;
}"""
eruda_injection = ""
if dev_tools_f12:
eruda_injection = """
view.evaluateJavascript("javascript:(function() { " +
"if(document.getElementById('eruda-plugin')) return; " +
"var script = document.createElement('script'); " +
"script.id = 'eruda-plugin'; " +
"script.src = 'https://cdn.jsdelivr.net/npm/eruda'; " +
"document.body.appendChild(script); " +
"script.onload = function() { eruda.init(); }; " +
"})();", null);"""
cache_mode = "WebSettings.LOAD_CACHE_ELSE_NETWORK" if smart_cache else "WebSettings.LOAD_DEFAULT"
network_reconnect = """
private void setupNetworkListener() {
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
cm.registerDefaultNetworkCallback(new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
runOnUiThread(() -> {
if (webView.getUrl() != null && webView.getUrl().contains("offline.html")) {
webView.loadUrl(TARGET_URL);
}
});
}
});
}
}"""
onesignal_setup = ""
if use_onesignal:
onesignal_setup = """
OneSignal.setLogLevel(OneSignal.LOG_LEVEL.VERBOSE, OneSignal.LOG_LEVEL.NONE);
OneSignal.initWithContext(this);
OneSignal.setAppId("{os_id}");
OneSignal.promptForPushNotifications();
""".format(os_id=onesignal_id)
content = """{imports_str}
public class MainActivity extends AppCompatActivity {{
private WebView webView;
private static final String TARGET_URL = "{target_url}";
private ValueCallback fileUploadCallback;
private static final int FILE_CHOOSER_RESULT = 1002;
private GoogleSignInClient mGoogleSignInClient;
private static final int RC_SIGN_IN = 9001;
@Override
protected void onCreate(Bundle savedInstanceState) {{
super.onCreate(savedInstanceState);
{root_det_c}
{screenshot_c}
{os_setup}
webView = new WebView(this);
webView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
webView.setBackgroundColor(Color.parseColor("#1a1a2e"));
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setDomStorageEnabled(true);
settings.setAllowFileAccess(true);
settings.setCacheMode({cache_mode});
settings.setDatabaseEnabled(true);
settings.setMixedContentMode({mixed_content});
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
cookieManager.setAcceptThirdPartyCookies(webView, true);
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken("{client_id}")
.requestEmail()
.build();
mGoogleSignInClient = GoogleSignIn.getClient(this, gso);
webView.addJavascriptInterface(new WebAppInterface(this), "AndroidBridge");
setupNetworkListener();
webView.setWebViewClient(new WebViewClient() {{
@Override
public void onPageFinished(WebView view, String url) {{
super.onPageFinished(view, url);
{eruda_inj}
}}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {{
super.onReceivedError(view, errorCode, description, failingUrl);
if (!isNetworkAvailable()) {{
view.loadUrl("file:///android_asset/offline.html");
}}
}}
}});
webView.setWebChromeClient(new WebChromeClient() {{
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {{
Log.d("SkyData-WebView", consoleMessage.message() + " -- From line "
+ consoleMessage.lineNumber() + " of "
+ consoleMessage.sourceId());
return super.onConsoleMessage(consoleMessage);
}}
@Override
public boolean onShowFileChooser(WebView wv, ValueCallback filePathCallback, FileChooserParams fileChooserParams) {{
fileUploadCallback = filePathCallback;
Intent intent = fileChooserParams.createIntent();
try {{
startActivityForResult(intent, FILE_CHOOSER_RESULT);
}} catch (Exception e) {{ return false; }}
return true;
}}
}});
setContentView(webView);
if (!isNetworkAvailable()) {{
webView.loadUrl("file:///android_asset/offline.html");
}} else {{
webView.loadUrl(TARGET_URL);
}}
}}
public class WebAppInterface {{
Context mContext;
WebAppInterface(Context c) {{ mContext = c; }}
@JavascriptInterface
public void startGoogleSignIn() {{
Intent signInIntent = mGoogleSignInClient.getSignInIntent();
startActivityForResult(signInIntent, RC_SIGN_IN);
}}
@JavascriptInterface
public String getSupabaseUrl() {{ return "{supa_url}"; }}
@JavascriptInterface
public String getSupabaseKey() {{ return "{supa_key}"; }}
}}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {{
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_SIGN_IN) {{
Task task = GoogleSignIn.getSignedInAccountFromIntent(data);
try {{
GoogleSignInAccount account = task.getResult(ApiException.class);
String idToken = account.getIdToken();
webView.post(() -> {{
webView.evaluateJavascript("javascript:handleGoogleLoginSuccess('" + idToken + "')", null);
}});
}} catch (ApiException e) {{
Log.e("SkyData-Auth", "Login Failed Code: " + e.getStatusCode());
webView.post(() -> {{
webView.evaluateJavascript("javascript:handleGoogleLoginError('" + e.getStatusCode() + "')", null);
}});
}}
return;
}}
}}
@Override
public void onBackPressed() {{
if (webView.canGoBack()) {{ webView.goBack(); }}
else {{ super.onBackPressed(); }}
}}
private boolean isNetworkAvailable() {{
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm != null) {{
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
}}
return false;
}}
{root_method}
{frida_method}
{tamper_method}
{memory_method}
{network_reconnect}
}}
""".format(
imports_str="\n".join(imports),
target_url=target_url.replace('"', '\\"'),
screenshot_c= "getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);" if prevent_screenshots else "",
cache_mode=cache_mode,
mixed_content=mixed_content,
client_id=google_web_client_id,
eruda_inj=eruda_injection,
supa_url=supabase_url,
supa_key=supabase_anon_key,
root_det_c=root_detection_code,
root_method=root_detection_method,
frida_method=frida_method,
tamper_method=tamper_method,
memory_method=memory_method,
network_reconnect=network_reconnect,
os_setup=onesignal_setup
)
with open(os.path.join(java_dir, "MainActivity.java"), "w") as f:
f.write(content)
def create_splash_activity(java_dir, config):
package_name = config.get("package_name", "com.example.webapp")
splash_text = config.get("splash_text", "Loading...")
splash_text_color = config.get("splash_text_color", "#FFFFFF")
splash_bg_color = config.get("splash_bg_color", "#1a1a2e")
has_splash_image = config.get("has_splash_image", False)
image_code = ""
if has_splash_image:
image_code = """
ImageView splashImage = new ImageView(this);
try {
java.io.InputStream is = getAssets().open("splash_media");
android.graphics.drawable.Drawable d = android.graphics.drawable.Drawable.createFromStream(is, null);
splashImage.setImageDrawable(d);
splashImage.setScaleType(ImageView.ScaleType.FIT_CENTER);
LinearLayout.LayoutParams imgParams = new LinearLayout.LayoutParams(600, 600);
imgParams.gravity = android.view.Gravity.CENTER;
splashImage.setLayoutParams(imgParams);
layout.addView(splashImage);
is.close();
} catch (Exception e) {
e.printStackTrace();
}"""
content = """package {package_name};
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.view.Gravity;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class SplashActivity extends AppCompatActivity {{
@Override
protected void onCreate(Bundle savedInstanceState) {{
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setGravity(Gravity.CENTER);
layout.setBackgroundColor(Color.parseColor("{bg_color}"));
layout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
{image_code}
TextView textView = new TextView(this);
textView.setText("{splash_text}");
textView.setTextColor(Color.parseColor("{text_color}"));
textView.setTextSize(24);
textView.setGravity(Gravity.CENTER);
LinearLayout.LayoutParams textParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
textParams.topMargin = 40;
textView.setLayoutParams(textParams);
layout.addView(textView);
setContentView(layout);
new Handler().postDelayed(new Runnable() {{
@Override
public void run() {{
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
startActivity(intent);
finish();
}}
}}, 3000);
}}
}}
""".format(
package_name=package_name,
bg_color=splash_bg_color if splash_bg_color else "#1a1a2e",
image_code=image_code,
splash_text=splash_text.replace('"', '\\"') if splash_text else "Loading...",
text_color=splash_text_color if splash_text_color else "#FFFFFF"
)
with open(os.path.join(java_dir, "SplashActivity.java"), "w") as f:
f.write(content)
def save_app_icon(res_dir, icon_b64):
sizes = {"mipmap-mdpi": 48, "mipmap-hdpi": 72, "mipmap-xhdpi": 96, "mipmap-xxhdpi": 144, "mipmap-xxxhdpi": 192}
try:
if "," in icon_b64:
icon_b64 = icon_b64.split(",")[1]
icon_bytes = base64.b64decode(icon_b64)
for folder, size in sizes.items():
mipmap_dir = os.path.join(res_dir, folder)
os.makedirs(mipmap_dir, exist_ok=True)
icon_path = os.path.join(mipmap_dir, "ic_launcher.png")
with open(icon_path, "wb") as f:
f.write(icon_bytes)
except Exception as e:
for folder, size in sizes.items():
mipmap_dir = os.path.join(res_dir, folder)
os.makedirs(mipmap_dir, exist_ok=True)
create_default_icon(os.path.join(mipmap_dir, "ic_launcher.png"))
def create_default_icon(path):
import struct, zlib
width = 48
height = 48
def create_png(w, h, r, g, b):
def chunk(chunk_type, data):
c = chunk_type + data
crc = zlib.crc32(c) & 0xFFFFFFFF
return struct.pack(">I", len(data)) + c + struct.pack(">I", crc)
header = b'\x89PNG\r\n\x1a\n'
ihdr = chunk(b'IHDR', struct.pack(">IIBBBBB", w, h, 8, 2, 0, 0, 0))
raw_data = b''
for y in range(h):
raw_data += b'\x00'
for x in range(w):
raw_data += bytes([r, g, b])
compressed = zlib.compress(raw_data)
idat = chunk(b'IDAT', compressed)
iend = chunk(b'IEND', b'')
return header + ihdr + idat + iend
png_data = create_png(width, height, 0x4e, 0x54, 0xc8)
with open(path, "wb") as f:
f.write(png_data)
def setup_project(build_id, config):
project_dir = os.path.join(BUILD_DIR_BASE, build_id, "project")
os.makedirs(project_dir, exist_ok=True)
package_name = config.get("package_name", "com.example.webapp")
package_path = package_name.replace(".", "/")
app_dir = os.path.join(project_dir, "app")
src_main = os.path.join(app_dir, "src", "main")
java_dir = os.path.join(src_main, "java", package_path)
res_dir = os.path.join(src_main, "res")
assets_dir = os.path.join(src_main, "assets")
os.makedirs(java_dir, exist_ok=True)
os.makedirs(res_dir, exist_ok=True)
os.makedirs(assets_dir, exist_ok=True)
os.makedirs(os.path.join(res_dir, "values"), exist_ok=True)
use_firebase = config.get("firebase_enabled", False)
use_onesignal = bool(config.get("onesignal_app_id", "").strip())
firebase_json = config.get("firebase_json", "")
actual_firebase_enabled = False
if use_firebase and firebase_json.strip():
try:
parsed = json.loads(firebase_json)
if "project_info" in parsed:
os.makedirs(app_dir, exist_ok=True)
with open(os.path.join(app_dir, "google-services.json"), "w") as f:
json.dump(parsed, f, indent=2)
actual_firebase_enabled = True
else:
LOG_STORE[build_id].append("⚠️ WARNING: Firebase JSON is missing 'project_info'. Firebase integration skipped.")
except json.JSONDecodeError:
LOG_STORE[build_id].append("⚠️ WARNING: Firebase JSON is invalid. Firebase integration skipped.")
config["actual_firebase_enabled"] = actual_firebase_enabled
create_build_gradle_root(project_dir, actual_firebase_enabled, use_onesignal)
create_settings_gradle(project_dir, config.get("app_name", "WebApp"))
create_gradle_properties(project_dir)
create_gradle_wrapper(project_dir)
create_app_build_gradle(app_dir, config)
create_proguard_rules(app_dir, config)
create_android_manifest(src_main, config)
create_main_activity(java_dir, config)
if config.get("splash_enabled"):
create_splash_activity(java_dir, config)
icon_b64 = config.get("app_icon_b64", "")
if icon_b64:
save_app_icon(res_dir, icon_b64)
else:
for folder in ["mipmap-mdpi", "mipmap-hdpi", "mipmap-xhdpi", "mipmap-xxhdpi", "mipmap-xxxhdpi"]:
mipmap_dir = os.path.join(res_dir, folder)
os.makedirs(mipmap_dir, exist_ok=True)
create_default_icon(os.path.join(mipmap_dir, "ic_launcher.png"))
splash_media_b64 = config.get("splash_media_b64", "")
if splash_media_b64 and config.get("splash_enabled"):
try:
if "," in splash_media_b64:
splash_media_b64 = splash_media_b64.split(",")[1]
media_bytes = base64.b64decode(splash_media_b64)
with open(os.path.join(assets_dir, "splash_media"), "wb") as f:
f.write(media_bytes)
config["has_splash_image"] = True
create_splash_activity(java_dir, config)
except Exception as e:
config["has_splash_image"] = False
if config.get("offline_mode", True):
with open(os.path.join(assets_dir, "offline.html"), "w") as f:
f.write(generate_offline_html())
strings_xml = """
{app_name}
""".format(app_name=config.get("app_name", "WebApp").replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """))
with open(os.path.join(res_dir, "values", "strings.xml"), "w") as f:
f.write(strings_xml)
colors_xml = """
{status_color}
{status_color}
#e94560
""".format(status_color=config.get("status_bar_color", "#1a1a2e"))
with open(os.path.join(res_dir, "values", "colors.xml"), "w") as f:
f.write(colors_xml)
styles_xml = """
"""
with open(os.path.join(res_dir, "values", "styles.xml"), "w") as f:
f.write(styles_xml)
if os.path.exists(KEYSTORE_PATH):
shutil.copy2(KEYSTORE_PATH, os.path.join(project_dir, "release-key.jks"))
local_props = "sdk.dir={}\n".format(os.environ.get("ANDROID_SDK_ROOT", "/opt/android-sdk"))
with open(os.path.join(project_dir, "local.properties"), "w") as f:
f.write(local_props)
return project_dir
def run_build(build_id, config):
LOG_STORE[build_id] = []
BUILD_STATUS[build_id] = "running"
def log(msg):
timestamp = datetime.now().strftime("%H:%M:%S")
LOG_STORE[build_id].append("[{}] {}".format(timestamp, msg))
try:
log("=== Build Started ===")
log("App Name: {}".format(config.get("app_name", "WebApp")))
log("Package: {}".format(config.get("package_name", "com.example.webapp")))
log("Setting up project structure...")
project_dir = setup_project(build_id, config)
log("Project structure created successfully.")
log("Starting Gradle build...")
build_format = config.get("build_format", "apk").lower()
if build_format == "aab":
gradle_task = "bundleRelease"
else:
gradle_task = "assembleRelease"
gradle_bin = os.path.join(os.environ.get("GRADLE_HOME", "/opt/gradle/gradle-7.6"), "bin", "gradle")
cmd = [
gradle_bin,
gradle_task,
"--parallel",
"--build-cache",
"--no-daemon",
"--stacktrace"
]
log("Running: {}".format(" ".join(cmd)))
env = os.environ.copy()
env["ANDROID_SDK_ROOT"] = os.environ.get("ANDROID_SDK_ROOT", "/opt/android-sdk")
env["ANDROID_HOME"] = os.environ.get("ANDROID_HOME", "/opt/android-sdk")
env["JAVA_HOME"] = os.environ.get("JAVA_HOME", "/usr/lib/jvm/java-17-openjdk-amd64")
process = subprocess.Popen(
cmd,
cwd=project_dir,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
env=env,
bufsize=1
)
for line in iter(process.stdout.readline, ""):
stripped = line.rstrip()
if stripped:
log(stripped)
process.wait()
if process.returncode == 0:
log("=== BUILD SUCCESSFUL ===")
if build_format == "aab":
output_path = os.path.join(project_dir, "app", "build", "outputs", "bundle", "release", "app-release.aab")
else:
base_apk_dir = os.path.join(project_dir, "app", "build", "outputs", "apk", "release")
output_path = os.path.join(base_apk_dir, "app-universal-release.apk")
if not os.path.exists(output_path):
output_path = os.path.join(base_apk_dir, "app-release.apk")
if not os.path.exists(output_path):
output_path = os.path.join(base_apk_dir, "app-release-unsigned.apk")
if os.path.exists(output_path):
artifact_name = "{}.{}".format(config.get("app_name", "app").replace(" ", "_"), "aab" if build_format == "aab" else "apk")
final_path = os.path.join(BUILD_DIR_BASE, build_id, artifact_name)
shutil.copy2(output_path, final_path)
BUILD_ARTIFACTS[build_id] = final_path
log("Artifact saved: {}".format(artifact_name))
BUILD_STATUS[build_id] = "success"
else:
log("ERROR: Build output file not found at expected path.")
BUILD_STATUS[build_id] = "failed"
else:
log("=== BUILD FAILED (exit code: {}) ===".format(process.returncode))
BUILD_STATUS[build_id] = "failed"
full_log = "\n".join(LOG_STORE[build_id])
ai_analysis = analyze_logs_ai(full_log)
log("")
log("=== AI Log Analysis (Arabic) ===")
for analysis_line in ai_analysis.split("\n"):
log(analysis_line)
except Exception as e:
log("CRITICAL ERROR: {}".format(str(e)))
BUILD_STATUS[build_id] = "failed"
# ==========================================
# دمج الواجهة (HTML) لكي لا تحتاج لملف index.html أبداً!
# ==========================================
HTML_CONTENT = """
Android APK/AAB Builder SaaS
"""
@app.route("/")
def index():
return Response(HTML_CONTENT, mimetype="text/html")
@app.route("/sha1")
def get_sha1_route():
sha1 = get_sha1()
if sha1:
return jsonify({"sha1": sha1, "formatted": sha1})
return jsonify({"error": "Keystore not found"}), 404
@app.route("/download_keystore")
def download_keystore():
if os.path.exists(KEYSTORE_PATH):
return send_file(KEYSTORE_PATH, as_attachment=True, download_name="release-key.jks")
return jsonify({"error": "Keystore not found"}), 404
@app.route("/build", methods=["POST"])
def start_build():
try:
data = request.form.to_dict()
config = {}
config["supabase_url"] = data.get("supabase_url", "")
config["supabase_anon_key"] = data.get("supabase_anon_key", "")
raw_google_id = data.get("google_web_client_id", "")
google_id_match = re.search(r'([0-9]+-[a-zA-Z0-9_]+\.apps\.googleusercontent\.com)', raw_google_id)
if google_id_match:
config["google_web_client_id"] = google_id_match.group(1)
else:
config["google_web_client_id"] = raw_google_id.strip()
config["app_name"] = data.get("app_name", "MyApp")
config["package_name"] = data.get("package_name", "com.example.myapp")
config["target_url"] = data.get("target_url", "https://www.google.com")
config["build_format"] = data.get("build_format", "apk")
config["version_name"] = data.get("version_name", "1.0.0")
config["version_code"] = data.get("version_code", "1")
config["screen_orientation"] = data.get("screen_orientation", "unspecified")
config["dev_tools_f12"] = data.get("dev_tools_f12", "on") == "on"
config["anti_frida"] = data.get("anti_frida") == "on"
config["smart_cache"] = data.get("smart_cache") == "on"
config["prevent_memory_edit"] = data.get("prevent_memory_edit") == "on"
config["split_apk"] = data.get("split_apk") == "on"
config["splash_enabled"] = data.get("splash_enabled") == "on"
config["splash_text"] = data.get("splash_text", "Loading...")
config["splash_text_color"] = data.get("splash_text_color", "#FFFFFF")
config["splash_bg_color"] = data.get("splash_bg_color", "#1a1a2e")
config["status_bar_color"] = data.get("status_bar_color", "#1a1a2e")
config["bottom_nav_enabled"] = data.get("bottom_nav_enabled") == "on"
config["enable_javascript"] = data.get("enable_javascript") == "on"
config["enable_dom_storage"] = data.get("enable_dom_storage") == "on"
config["allow_file_access"] = data.get("allow_file_access") == "on"
config["media_autoplay"] = data.get("media_autoplay") == "on"
config["enable_zoom"] = data.get("enable_zoom") == "on"
config["custom_user_agent"] = data.get("custom_user_agent", "")
config["pull_to_refresh"] = data.get("pull_to_refresh") == "on"
config["multi_windows"] = data.get("multi_windows") == "on"
config["deep_link_intercept"] = data.get("deep_link_intercept") == "on"
config["proguard_enabled"] = data.get("proguard_enabled") == "on"
config["root_detection"] = data.get("root_detection") == "on"
config["prevent_screenshots"] = data.get("prevent_screenshots") == "on"
config["clear_cache_exit"] = data.get("clear_cache_exit") == "on"
config["clear_cookies_exit"] = data.get("clear_cookies_exit") == "on"
config["keep_screen_on"] = data.get("keep_screen_on") == "on"
config["fullscreen_mode"] = data.get("fullscreen_mode") == "on"
config["enforce_https"] = data.get("enforce_https") == "on"
config["tamper_protection"] = data.get("tamper_protection") == "on"
config["firebase_enabled"] = data.get("firebase_enabled") == "on"
config["firebase_json"] = data.get("firebase_json", "")
config["onesignal_enabled"] = bool(data.get("onesignal_app_id", "").strip())
config["onesignal_app_id"] = data.get("onesignal_app_id", "")
config["admob_enabled"] = data.get("admob_enabled") == "on"
config["admob_banner_id"] = data.get("admob_banner_id", "")
config["admob_interstitial_id"] = data.get("admob_interstitial_id", "")
config["perm_camera"] = data.get("perm_camera") == "on"
config["perm_microphone"] = data.get("perm_microphone") == "on"
config["perm_location"] = data.get("perm_location") == "on"
config["perm_storage"] = data.get("perm_storage") == "on"
config["perm_vibrate"] = data.get("perm_vibrate") == "on"
config["perm_contacts"] = data.get("perm_contacts") == "on"
config["perm_notifications"] = data.get("perm_notifications") == "on"
config["offline_mode"] = data.get("offline_mode") == "on"
config["app_icon_b64"] = data.get("app_icon_b64", "")
config["splash_media_b64"] = data.get("splash_media_b64", "")
config["has_splash_image"] = bool(data.get("splash_media_b64", "").strip())
build_id = str(uuid.uuid4())[:12]
os.makedirs(os.path.join(BUILD_DIR_BASE, build_id), exist_ok=True)
thread = threading.Thread(target=run_build, args=(build_id, config), daemon=True)
thread.start()
return jsonify({"status": "started", "build_id": build_id})
except Exception as e:
return jsonify({"status": "error", "message": str(e)}), 500
@app.route("/logs/")
def get_logs(build_id):
def generate():
last_index = 0
while True:
logs = LOG_STORE.get(build_id, [])
status = BUILD_STATUS.get(build_id, "unknown")
if last_index < len(logs):
new_logs = logs[last_index:]
last_index = len(logs)
for log_line in new_logs:
yield "data: {}\n\n".format(json.dumps({"type": "log", "message": log_line}))
if status in ("success", "failed"):
yield "data: {}\n\n".format(json.dumps({"type": "status", "status": status, "build_id": build_id}))
break
time.sleep(0.5)
return Response(generate(), mimetype="text/event-stream", headers={
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no",
"Connection": "keep-alive"
})
@app.route("/download/")
def download_artifact(build_id):
artifact_path = BUILD_ARTIFACTS.get(build_id)
if artifact_path and os.path.exists(artifact_path):
return send_file(artifact_path, as_attachment=True, download_name=os.path.basename(artifact_path))
else:
return jsonify({"error": "Artifact not found"}), 404
@app.route("/status/")
def get_status(build_id):
status = BUILD_STATUS.get(build_id, "unknown")
return jsonify({"status": status, "build_id": build_id})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=7860, debug=False, threaded=True)