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, render_template, send_file, Response app = Flask(__name__) BUILD_DIR_BASE = "/tmp/builds" LOG_STORE = {} BUILD_STATUS = {} BUILD_ARTIFACTS = {} os.makedirs(BUILD_DIR_BASE, exist_ok=True) 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
🔌

No Internet Connection

Please check your network settings and try again.

""" def generate_keystore(build_dir, dev_name, dev_email): keystore_path = os.path.join(build_dir, "release-key.jks") keytool_cmd = [ "keytool", "-genkeypair", "-v", "-keystore", keystore_path, "-keyalg", "RSA", "-keysize", "2048", "-validity", "10000", "-alias", "releasekey", "-storepass", "android123", "-keypass", "android123", "-dname", "CN={}, OU=Mobile, O=Company, L=City, S=State, C=US".format(dev_name if dev_name else "Developer") ] subprocess.run(keytool_cmd, capture_output=True, timeout=60) return keystore_path def create_build_gradle_root(project_dir, use_firebase, use_onesignal): firebase_classpath = "" if use_firebase: firebase_classpath = " classpath 'com.google.gms:google-services:4.4.0'" content = """buildscript {{ repositories {{ google() mavenCentral() }} dependencies {{ classpath 'com.android.tools.build:gradle:7.4.2' {firebase_cp} }} }} allprojects {{ repositories {{ google() mavenCentral() }} }} task clean(type: Delete) {{ delete rootProject.buildDir }} """.format(firebase_cp=firebase_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" # 🔥 Use the securely validated flag from config use_firebase = config.get("actual_firebase_enabled", False) use_onesignal = config.get("onesignal_enabled", False) onesignal_id = config.get("onesignal_app_id", "") 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'") 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_admob: dependencies.append(" implementation 'com.google.android.gms:play-services-ads:22.6.0'") deps_str = "\n".join(dependencies) firebase_plugin = "" if use_firebase: firebase_plugin = "apply plugin: 'com.google.gms.google-services'" proguard_rules = "" if config.get("proguard_enabled"): proguard_rules = """ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'""" content = """plugins {{ id 'com.android.application' }} {firebase_plugin} 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 }} }} compileOptions {{ sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 }} lint {{ abortOnError false checkReleaseBuilds false }} }} dependencies {{ {deps_str} }} """.format( firebase_plugin=firebase_plugin, package_name=package_name, version_code=version_code, version_name=version_name, minify_enabled=minify_enabled, proguard_rules=proguard_rules, 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.** { *; } -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(' ') 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) root_detection = config.get("root_detection", False) tamper_protection = config.get("tamper_protection", False) 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" perm_camera = config.get("perm_camera", False) perm_microphone = config.get("perm_microphone", False) perm_location = config.get("perm_location", False) perm_storage = config.get("perm_storage", False) perm_notifications = config.get("perm_notifications", False) use_admob = config.get("admob_enabled", False) admob_banner_id = config.get("admob_banner_id", "") admob_interstitial_id = config.get("admob_interstitial_id", "") 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.NetworkInfo;") imports.append("import android.net.Uri;") imports.append("import android.os.Build;") imports.append("import android.os.Bundle;") imports.append("import android.view.View;") imports.append("import android.view.Window;") imports.append("import android.view.WindowManager;") imports.append("import android.webkit.CookieManager;") imports.append("import android.webkit.GeolocationPermissions;") 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;") if pull_to_refresh: imports.append("import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;") if use_admob: imports.append("import com.google.android.gms.ads.AdRequest;") imports.append("import com.google.android.gms.ads.AdSize;") imports.append("import com.google.android.gms.ads.AdView;") imports.append("import com.google.android.gms.ads.MobileAds;") imports.append("import com.google.android.gms.ads.interstitial.InterstitialAd;") imports.append("import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback;") imports.append("import com.google.android.gms.ads.LoadAdError;") imports.append("import java.io.File;") imports.append("import java.util.ArrayList;") imports.append("import java.util.List;") imports.append("") runtime_perms_list = [] if perm_camera: runtime_perms_list.append('"android.permission.CAMERA"') if perm_microphone: runtime_perms_list.append('"android.permission.RECORD_AUDIO"') if perm_location: runtime_perms_list.append('"android.permission.ACCESS_FINE_LOCATION"') runtime_perms_list.append('"android.permission.ACCESS_COARSE_LOCATION"') if perm_storage: runtime_perms_list.append('"android.permission.READ_EXTERNAL_STORAGE"') if perm_notifications: runtime_perms_list.append('"android.permission.POST_NOTIFICATIONS"') perms_array = ", ".join(runtime_perms_list) root_detection_code = "" if root_detection: root_detection_code = """ if (isRootedOrEmulator()) { Toast.makeText(this, "This app cannot run on rooted/emulated devices.", Toast.LENGTH_LONG).show(); finish(); return; }""" 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"}; 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") || Build.PRODUCT.contains("sdk_gphone") || Build.PRODUCT.contains("vbox86p") || Build.HARDWARE.contains("goldfish") || Build.HARDWARE.contains("ranchu")) return true; return false; }""" 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; }""" tamper_check = "" if tamper_protection: tamper_check = """ if (!verifyAppSignature()) { Toast.makeText(this, "App integrity check failed.", Toast.LENGTH_LONG).show(); finish(); return; }""" screenshot_code = "" if prevent_screenshots: screenshot_code = " getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);" keep_screen_code = "" if keep_screen_on: keep_screen_code = " getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);" fullscreen_code = "" if fullscreen_mode: fullscreen_code = """ getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; }""" status_bar_code = """ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {{ Window window = getWindow(); window.setStatusBarColor(Color.parseColor("{color}")); }}""".format(color=status_bar_color if status_bar_color else "#1a1a2e") swipe_refresh_setup = "" swipe_refresh_decl = "" if pull_to_refresh: swipe_refresh_decl = " private SwipeRefreshLayout swipeRefreshLayout;" swipe_refresh_setup = """ swipeRefreshLayout = new SwipeRefreshLayout(this); swipeRefreshLayout.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { webView.reload(); } });""" ua_code = "" if custom_ua: ua_code = ' settings.setUserAgentString("{}");'.format(custom_ua.replace('"', '\\"')) admob_decl = "" admob_setup = "" admob_interstitial_setup = "" admob_interstitial_decl = "" if use_admob: admob_decl = " private AdView adView;" admob_interstitial_decl = " private InterstitialAd mInterstitialAd;" admob_setup = """ MobileAds.initialize(this, initializationStatus -> {{}}); adView = new AdView(this); adView.setAdSize(AdSize.BANNER); adView.setAdUnitId("{banner_id}"); AdRequest adRequest = new AdRequest.Builder().build(); adView.loadAd(adRequest);""".format(banner_id=admob_banner_id if admob_banner_id else "ca-app-pub-3940256099942544/6300978111") if admob_interstitial_id: admob_interstitial_setup = """ loadInterstitialAd();""" admob_interstitial_method = "" if use_admob and admob_interstitial_id: admob_interstitial_method = """ private void loadInterstitialAd() {{ AdRequest adRequest = new AdRequest.Builder().build(); InterstitialAd.load(this, "{interstitial_id}", adRequest, new InterstitialAdLoadCallback() {{ @Override public void onAdLoaded(InterstitialAd interstitialAd) {{ mInterstitialAd = interstitialAd; }} @Override public void onAdFailedToLoad(LoadAdError loadAdError) {{ mInterstitialAd = null; }} }}); }} private void showInterstitialAd() {{ if (mInterstitialAd != null) {{ mInterstitialAd.show(this); loadInterstitialAd(); }} }}""".format(interstitial_id=admob_interstitial_id) deep_link_code = "" if deep_link_intercept: deep_link_code = """ String host = Uri.parse(url).getHost(); if (host != null && (host.contains("accounts.google.com") || host.contains("facebook.com") || host.contains("wa.me") || host.contains("whatsapp.com") || host.contains("t.me") || host.contains("twitter.com") || host.contains("x.com") || host.contains("instagram.com"))) { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); startActivity(intent); return true; }""" offline_check_code = "" if offline_mode: offline_check_code = """ if (!isNetworkAvailable()) { webView.loadUrl("file:///android_asset/offline.html"); } else { webView.loadUrl(TARGET_URL); }""" else: offline_check_code = """ webView.loadUrl(TARGET_URL);""" offline_method = "" if offline_mode: offline_method = """ private boolean isNetworkAvailable() { ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); if (cm != null) { NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); return activeNetwork != null && activeNetwork.isConnectedOrConnecting(); } return false; }""" on_destroy_code = "" destroy_lines = [] if clear_cache_exit: destroy_lines.append(" if (webView != null) { webView.clearCache(true); }") if clear_cookies_exit: destroy_lines.append(" CookieManager.getInstance().removeAllCookies(null);") destroy_lines.append(" CookieManager.getInstance().flush();") if destroy_lines: on_destroy_code = """ @Override protected void onDestroy() {{ super.onDestroy(); {lines} }}""".format(lines="\n".join(destroy_lines)) request_perms_code = "" if runtime_perms_list: request_perms_code = """ List permissionsNeeded = new ArrayList<>(); String[] allPerms = new String[]{{{perms}}}; for (String perm : allPerms) {{ if (ContextCompat.checkSelfPermission(this, perm) != PackageManager.PERMISSION_GRANTED) {{ permissionsNeeded.add(perm); }} }} if (!permissionsNeeded.isEmpty()) {{ ActivityCompat.requestPermissions(this, permissionsNeeded.toArray(new String[0]), 1001); }}""".format(perms=perms_array) bottom_nav_code_layout = "" bottom_nav_code_setup = "" if bottom_nav: bottom_nav_code_layout = """ LinearLayout bottomNav = new LinearLayout(this); bottomNav.setOrientation(LinearLayout.HORIZONTAL); bottomNav.setBackgroundColor(Color.parseColor("#1a1a2e")); bottomNav.setGravity(android.view.Gravity.CENTER); LinearLayout.LayoutParams navParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 130); bottomNav.setLayoutParams(navParams); ImageButton btnBack = new ImageButton(this); btnBack.setImageResource(android.R.drawable.ic_media_previous); btnBack.setBackgroundColor(Color.TRANSPARENT); btnBack.setColorFilter(Color.WHITE); LinearLayout.LayoutParams btnParams = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1); btnBack.setLayoutParams(btnParams); btnBack.setOnClickListener(v -> { if (webView.canGoBack()) webView.goBack(); }); ImageButton btnHome = new ImageButton(this); btnHome.setImageResource(android.R.drawable.ic_menu_revert); btnHome.setBackgroundColor(Color.TRANSPARENT); btnHome.setColorFilter(Color.WHITE); btnHome.setLayoutParams(btnParams); btnHome.setOnClickListener(v -> webView.loadUrl(TARGET_URL)); ImageButton btnRefresh = new ImageButton(this); btnRefresh.setImageResource(android.R.drawable.ic_popup_sync); btnRefresh.setBackgroundColor(Color.TRANSPARENT); btnRefresh.setColorFilter(Color.WHITE); btnRefresh.setLayoutParams(btnParams); btnRefresh.setOnClickListener(v -> webView.reload()); bottomNav.addView(btnBack); bottomNav.addView(btnHome); bottomNav.addView(btnRefresh);""" build_layout = "" if pull_to_refresh and bottom_nav and use_admob: build_layout = """ LinearLayout rootLayout = new LinearLayout(this); rootLayout.setOrientation(LinearLayout.VERTICAL); rootLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT)); FrameLayout.LayoutParams webParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, 0); LinearLayout.LayoutParams webLinearParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1); swipeRefreshLayout.addView(webView); swipeRefreshLayout.setLayoutParams(webLinearParams); rootLayout.addView(swipeRefreshLayout); rootLayout.addView(adView); {bottom_nav_layout} rootLayout.addView(bottomNav); setContentView(rootLayout);""".format(bottom_nav_layout=bottom_nav_code_layout) elif pull_to_refresh and bottom_nav: build_layout = """ LinearLayout rootLayout = new LinearLayout(this); rootLayout.setOrientation(LinearLayout.VERTICAL); rootLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT)); LinearLayout.LayoutParams webLinearParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1); swipeRefreshLayout.addView(webView); swipeRefreshLayout.setLayoutParams(webLinearParams); rootLayout.addView(swipeRefreshLayout); {bottom_nav_layout} rootLayout.addView(bottomNav); setContentView(rootLayout);""".format(bottom_nav_layout=bottom_nav_code_layout) elif pull_to_refresh and use_admob: build_layout = """ LinearLayout rootLayout = new LinearLayout(this); rootLayout.setOrientation(LinearLayout.VERTICAL); rootLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT)); LinearLayout.LayoutParams webLinearParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1); swipeRefreshLayout.addView(webView); swipeRefreshLayout.setLayoutParams(webLinearParams); rootLayout.addView(swipeRefreshLayout); rootLayout.addView(adView); setContentView(rootLayout);""" elif bottom_nav and use_admob: build_layout = """ LinearLayout rootLayout = new LinearLayout(this); rootLayout.setOrientation(LinearLayout.VERTICAL); rootLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT)); LinearLayout.LayoutParams webLinearParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1); webView.setLayoutParams(webLinearParams); rootLayout.addView(webView); rootLayout.addView(adView); {bottom_nav_layout} rootLayout.addView(bottomNav); setContentView(rootLayout);""".format(bottom_nav_layout=bottom_nav_code_layout) elif pull_to_refresh: build_layout = """ swipeRefreshLayout.addView(webView); setContentView(swipeRefreshLayout);""" elif bottom_nav: build_layout = """ LinearLayout rootLayout = new LinearLayout(this); rootLayout.setOrientation(LinearLayout.VERTICAL); rootLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT)); LinearLayout.LayoutParams webLinearParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1); webView.setLayoutParams(webLinearParams); rootLayout.addView(webView); {bottom_nav_layout} rootLayout.addView(bottomNav); setContentView(rootLayout);""".format(bottom_nav_layout=bottom_nav_code_layout) elif use_admob: build_layout = """ LinearLayout rootLayout = new LinearLayout(this); rootLayout.setOrientation(LinearLayout.VERTICAL); rootLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT)); LinearLayout.LayoutParams webLinearParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1); webView.setLayoutParams(webLinearParams); rootLayout.addView(webView); rootLayout.addView(adView); setContentView(rootLayout);""" else: build_layout = """ setContentView(webView);""" swipe_page_finished = "" if pull_to_refresh: swipe_page_finished = """ if (swipeRefreshLayout != null) { swipeRefreshLayout.setRefreshing(false); }""" 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; {swipe_decl} {admob_d} {admob_interstitial_d} @Override protected void onCreate(Bundle savedInstanceState) {{ super.onCreate(savedInstanceState); {screenshot_c} {keep_screen_c} {fullscreen_c} {status_bar_c} {root_det_c} {tamper_c} 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({js_enabled}); settings.setDomStorageEnabled({dom_enabled}); settings.setAllowFileAccess({file_access}); settings.setMediaPlaybackRequiresUserGesture({media_gesture}); settings.setSupportZoom({zoom_enabled}); settings.setBuiltInZoomControls({zoom_enabled}); settings.setDisplayZoomControls(false); settings.setJavaScriptCanOpenWindowsAutomatically({multi_win}); settings.setSupportMultipleWindows({multi_win}); settings.setMixedContentMode({mixed_content}); settings.setLoadWithOverviewMode(true); settings.setUseWideViewPort(true); settings.setCacheMode(WebSettings.LOAD_DEFAULT); settings.setDatabaseEnabled(true); settings.setAllowContentAccess(true); {ua_c} CookieManager cookieManager = CookieManager.getInstance(); cookieManager.setAcceptCookie(true); cookieManager.setAcceptThirdPartyCookies(webView, true); {swipe_setup} {admob_s} {admob_inter_s} {request_perms} webView.setWebViewClient(new WebViewClient() {{ @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {{ String url = request.getUrl().toString(); {deep_link} if (url.startsWith("tel:") || url.startsWith("mailto:") || url.startsWith("sms:") || url.startsWith("whatsapp:") || url.startsWith("intent:")) {{ try {{ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); startActivity(intent); }} catch (Exception e) {{ e.printStackTrace(); }} return true; }} return false; }} @Override public void onPageStarted(WebView view, String url, Bitmap favicon) {{ super.onPageStarted(view, url, favicon); }} @Override public void onPageFinished(WebView view, String url) {{ super.onPageFinished(view, url); {swipe_finished} }} @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {{ super.onReceivedError(view, errorCode, description, failingUrl); {offline_on_error} }} }}); webView.setWebChromeClient(new WebChromeClient() {{ @Override public void onPermissionRequest(final PermissionRequest request) {{ runOnUiThread(() -> request.grant(request.getResources())); }} @Override public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {{ callback.invoke(origin, true, false); }} @Override public boolean onShowFileChooser(WebView wv, ValueCallback filePathCallback, FileChooserParams fileChooserParams) {{ if (fileUploadCallback != null) {{ fileUploadCallback.onReceiveValue(null); }} fileUploadCallback = filePathCallback; Intent intent = fileChooserParams.createIntent(); try {{ startActivityForResult(intent, FILE_CHOOSER_RESULT); }} catch (Exception e) {{ fileUploadCallback = null; return false; }} return true; }} }}); {build_layout_code} {offline_check} }} @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {{ super.onActivityResult(requestCode, resultCode, data); if (requestCode == FILE_CHOOSER_RESULT) {{ if (fileUploadCallback != null) {{ Uri[] results = null; if (resultCode == Activity.RESULT_OK && data != null) {{ String dataString = data.getDataString(); if (dataString != null) {{ results = new Uri[]{{Uri.parse(dataString)}}; }} }} fileUploadCallback.onReceiveValue(results); fileUploadCallback = null; }} }} }} @Override public void onBackPressed() {{ if (webView.canGoBack()) {{ webView.goBack(); }} else {{ super.onBackPressed(); }} }} {offline_method_code} {root_method} {tamper_method_code} {admob_inter_method} {on_destroy} }} """.format( imports_str="\n".join(imports), target_url=target_url.replace('"', '\\"'), swipe_decl=swipe_refresh_decl, admob_d=admob_decl, admob_interstitial_d=admob_interstitial_decl, screenshot_c=screenshot_code, keep_screen_c=keep_screen_code, fullscreen_c=fullscreen_code, status_bar_c=status_bar_code, root_det_c=root_detection_code, tamper_c=tamper_check, js_enabled="true" if enable_js else "false", dom_enabled="true" if enable_dom else "false", file_access="true" if allow_file_access else "false", media_gesture="false" if media_autoplay else "true", zoom_enabled="true" if enable_zoom else "false", multi_win="true" if multi_windows else "false", mixed_content=mixed_content, ua_c=ua_code, swipe_setup=swipe_refresh_setup, admob_s=admob_setup, admob_inter_s=admob_interstitial_setup, request_perms=request_perms_code, deep_link=deep_link_code, swipe_finished=swipe_page_finished, offline_on_error=' if (!isNetworkAvailable()) { view.loadUrl("file:///android_asset/offline.html"); }' if offline_mode else "", build_layout_code=build_layout, offline_check=offline_check_code, offline_method_code=offline_method, root_method=root_detection_method, tamper_method_code=tamper_method, admob_inter_method=admob_interstitial_method, on_destroy=on_destroy_code ) 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 import 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) # 🔥 FIREBASE VALIDATION LOGIC 🔥 use_firebase = config.get("firebase_enabled", False) use_onesignal = config.get("onesignal_enabled", False) firebase_json = config.get("firebase_json", "") actual_firebase_enabled = False if use_firebase and firebase_json.strip(): try: # Check if it's valid JSON AND contains the crucial "project_info" key parsed = json.loads(firebase_json) if "project_info" in parsed: # Only write the file and enable the plugin if it's perfectly valid 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 to prevent build failure.") except json.JSONDecodeError: LOG_STORE[build_id].append("⚠️ WARNING: Firebase JSON is invalid. Firebase integration skipped to prevent build failure.") elif use_firebase: LOG_STORE[build_id].append("⚠️ WARNING: Firebase was enabled but no JSON was provided. Integration skipped.") # Save actual state to config so create_app_build_gradle knows what to do 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) generate_keystore(os.path.join(BUILD_DIR_BASE, build_id, "project"), config.get("dev_name", "Developer"), config.get("dev_email", "dev@example.com")) 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("Build Format: {}".format(config.get("build_format", "apk").upper())) 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: output_path = os.path.join(project_dir, "app", "build", "outputs", "apk", "release", "app-release.apk") if not os.path.exists(output_path): output_path = os.path.join(project_dir, "app", "build", "outputs", "apk", "release", "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.") log("Searching for output files...") for root, dirs, files in os.walk(os.path.join(project_dir, "app", "build", "outputs")): for fname in files: log(" Found: {}".format(os.path.join(root, fname))) 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))) import traceback for tb_line in traceback.format_exc().split("\n"): log(tb_line) BUILD_STATUS[build_id] = "failed" @app.route("/") def index(): return render_template("index.html") @app.route("/build", methods=["POST"]) def start_build(): try: data = request.form.to_dict() config = {} 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_name"] = data.get("dev_name", "Developer") config["dev_email"] = data.get("dev_email", "dev@example.com") 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)