Spaces:
Paused
Paused
Create app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,1626 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import json
|
| 4 |
+
import uuid
|
| 5 |
+
import time
|
| 6 |
+
import shutil
|
| 7 |
+
import base64
|
| 8 |
+
import threading
|
| 9 |
+
import subprocess
|
| 10 |
+
import re
|
| 11 |
+
from datetime import datetime
|
| 12 |
+
from flask import Flask, request, jsonify, render_template, send_file, Response
|
| 13 |
+
|
| 14 |
+
app = Flask(__name__)
|
| 15 |
+
|
| 16 |
+
BUILD_DIR_BASE = "/tmp/builds"
|
| 17 |
+
LOG_STORE = {}
|
| 18 |
+
BUILD_STATUS = {}
|
| 19 |
+
BUILD_ARTIFACTS = {}
|
| 20 |
+
|
| 21 |
+
os.makedirs(BUILD_DIR_BASE, exist_ok=True)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def analyze_logs_ai(log_text):
|
| 25 |
+
diagnosis_lines = []
|
| 26 |
+
if "Could not resolve" in log_text or "Could not find" in log_text:
|
| 27 |
+
diagnosis_lines.append("خطأ في التبعيات: لم يتم العثور على مكتبة مطلوبة. تأكد من أن جميع المكتبات المحددة متوفرة في مستودعات Maven/Google.")
|
| 28 |
+
if "FirebaseApp" in log_text or "google-services" in log_text or "com.google.gms" in log_text:
|
| 29 |
+
diagnosis_lines.append("خطأ في Firebase: ملف google-services.json غير صالح أو غير متوافق مع اسم الحزمة. تحقق من إعدادات Firebase الخاصة بك.")
|
| 30 |
+
if "AAPT" in log_text or "resource" in log_text.lower() and "not found" in log_text.lower():
|
| 31 |
+
diagnosis_lines.append("خطأ في الموارد: مورد XML أو صورة مفقودة. تأكد من صحة جميع ملفات الموارد.")
|
| 32 |
+
if "Syntax error" in log_text or "Unexpected token" in log_text or "illegal start" in log_text.lower():
|
| 33 |
+
diagnosis_lines.append("خطأ في بناء الجملة: يوجد خطأ نحوي في كود Java. راجع ملفات المصدر بعناية.")
|
| 34 |
+
if "OutOfMemoryError" in log_text or "heap" in log_text.lower():
|
| 35 |
+
diagnosis_lines.append("خطأ في الذاكرة: نفدت ذاكرة JVM أثناء البناء. حاول زيادة حجم الذاكرة المخصصة.")
|
| 36 |
+
if "SDK location not found" in log_text or "ANDROID_HOME" in log_text:
|
| 37 |
+
diagnosis_lines.append("خطأ في SDK: لم يتم العثور على Android SDK. تحقق من متغيرات البيئة.")
|
| 38 |
+
if "Manifest merger failed" in log_text:
|
| 39 |
+
diagnosis_lines.append("خطأ في دمج الـ Manifest: تعارض بين إعدادات AndroidManifest.xml. راجع الأذونات والإعدادات.")
|
| 40 |
+
if "Execution failed for task" in log_text:
|
| 41 |
+
task_match = re.search(r"Execution failed for task '([^']+)'", log_text)
|
| 42 |
+
if task_match:
|
| 43 |
+
diagnosis_lines.append("فشل تنفيذ المهمة: {} - تحقق من إعدادات هذه المهمة والتبعيات المرتبطة بها.".format(task_match.group(1)))
|
| 44 |
+
if "minSdkVersion" in log_text:
|
| 45 |
+
diagnosis_lines.append("خطأ في إصدار SDK الأدنى: إصدار SDK الأدنى المحدد غير متوافق مع إحدى المكتبات.")
|
| 46 |
+
if "Duplicate class" in log_text:
|
| 47 |
+
diagnosis_lines.append("خطأ تكرار: يوجد تكرار في الفئات بين المكتبات. استخدم exclude لحل التعارضات.")
|
| 48 |
+
if "R8" in log_text and ("error" in log_text.lower() or "failed" in log_text.lower()):
|
| 49 |
+
diagnosis_lines.append("خطأ في R8/ProGuard: فشل تقليص الكود. تحقق من قواعد ProGuard وتأكد من عدم حذف فئات مطلوبة.")
|
| 50 |
+
if not diagnosis_lines:
|
| 51 |
+
if "BUILD SUCCESSFUL" in log_text:
|
| 52 |
+
diagnosis_lines.append("تم البناء بنجاح! لا توجد أخطاء.")
|
| 53 |
+
else:
|
| 54 |
+
diagnosis_lines.append("لم يتم التعرف على خطأ محدد. راجع سجل البناء الكامل يدوياً للبحث عن المشكلة.")
|
| 55 |
+
return "\n".join(diagnosis_lines)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def generate_offline_html():
|
| 59 |
+
return """<!DOCTYPE html>
|
| 60 |
+
<html lang="en">
|
| 61 |
+
<head>
|
| 62 |
+
<meta charset="UTF-8">
|
| 63 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 64 |
+
<title>Offline</title>
|
| 65 |
+
<style>
|
| 66 |
+
*{margin:0;padding:0;box-sizing:border-box}
|
| 67 |
+
body{font-family:'Segoe UI',sans-serif;background:#1a1a2e;color:#e0e0e0;display:flex;justify-content:center;align-items:center;min-height:100vh;text-align:center;padding:20px}
|
| 68 |
+
.container{max-width:400px}
|
| 69 |
+
h1{font-size:2em;margin-bottom:10px;color:#e94560}
|
| 70 |
+
p{font-size:1.1em;color:#aaa;margin-bottom:20px}
|
| 71 |
+
.icon{font-size:4em;margin-bottom:20px}
|
| 72 |
+
button{background:#e94560;color:#fff;border:none;padding:12px 30px;border-radius:8px;font-size:1em;cursor:pointer}
|
| 73 |
+
button:hover{background:#c73652}
|
| 74 |
+
</style>
|
| 75 |
+
</head>
|
| 76 |
+
<body>
|
| 77 |
+
<div class="container">
|
| 78 |
+
<div class="icon">🔌</div>
|
| 79 |
+
<h1>No Internet Connection</h1>
|
| 80 |
+
<p>Please check your network settings and try again.</p>
|
| 81 |
+
<button onclick="location.reload()">Retry</button>
|
| 82 |
+
</div>
|
| 83 |
+
</body>
|
| 84 |
+
</html>"""
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def generate_keystore(build_dir, dev_name, dev_email, build_id):
|
| 88 |
+
keystore_path = os.path.join(build_dir, "release-key.jks")
|
| 89 |
+
keytool_cmd = [
|
| 90 |
+
"keytool", "-genkeypair",
|
| 91 |
+
"-v",
|
| 92 |
+
"-keystore", keystore_path,
|
| 93 |
+
"-keyalg", "RSA",
|
| 94 |
+
"-keysize", "2048",
|
| 95 |
+
"-validity", "10000",
|
| 96 |
+
"-alias", "releasekey",
|
| 97 |
+
"-storepass", "android123",
|
| 98 |
+
"-keypass", "android123",
|
| 99 |
+
"-dname", "CN={}, OU=Mobile, O=Company, L=City, S=State, C=US".format(dev_name if dev_name else "Developer")
|
| 100 |
+
]
|
| 101 |
+
subprocess.run(keytool_cmd, capture_output=True, timeout=60)
|
| 102 |
+
|
| 103 |
+
# Extract SHA-1
|
| 104 |
+
list_cmd = [
|
| 105 |
+
"keytool", "-list", "-v",
|
| 106 |
+
"-keystore", keystore_path,
|
| 107 |
+
"-alias", "releasekey",
|
| 108 |
+
"-storepass", "android123",
|
| 109 |
+
"-keypass", "android123"
|
| 110 |
+
]
|
| 111 |
+
list_process = subprocess.run(list_cmd, capture_output=True, text=True)
|
| 112 |
+
sha1_match = re.search(r"SHA1:\s+([A-Fa-f0-9:]+)", list_process.stdout)
|
| 113 |
+
if sha1_match and build_id in LOG_STORE:
|
| 114 |
+
LOG_STORE[build_id].append("🟢 [IMPORTANT] Your SHA-1 Fingerprint is: {} (Copy this to Google Cloud Console)".format(sha1_match.group(1)))
|
| 115 |
+
|
| 116 |
+
return keystore_path
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def create_build_gradle_root(project_dir, use_firebase, use_onesignal):
|
| 120 |
+
firebase_classpath = ""
|
| 121 |
+
if use_firebase:
|
| 122 |
+
firebase_classpath = " classpath 'com.google.gms:google-services:4.4.0'"
|
| 123 |
+
content = """buildscript {{
|
| 124 |
+
repositories {{
|
| 125 |
+
google()
|
| 126 |
+
mavenCentral()
|
| 127 |
+
}}
|
| 128 |
+
dependencies {{
|
| 129 |
+
classpath 'com.android.tools.build:gradle:7.4.2'
|
| 130 |
+
{firebase_cp}
|
| 131 |
+
}}
|
| 132 |
+
}}
|
| 133 |
+
|
| 134 |
+
allprojects {{
|
| 135 |
+
repositories {{
|
| 136 |
+
google()
|
| 137 |
+
mavenCentral()
|
| 138 |
+
}}
|
| 139 |
+
}}
|
| 140 |
+
|
| 141 |
+
task clean(type: Delete) {{
|
| 142 |
+
delete rootProject.buildDir
|
| 143 |
+
}}
|
| 144 |
+
""".format(firebase_cp=firebase_classpath)
|
| 145 |
+
with open(os.path.join(project_dir, "build.gradle"), "w") as f:
|
| 146 |
+
f.write(content)
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
def create_settings_gradle(project_dir, app_name):
|
| 150 |
+
content = """rootProject.name = "{app_name}"
|
| 151 |
+
include ':app'
|
| 152 |
+
""".format(app_name=app_name.replace('"', '\\"'))
|
| 153 |
+
with open(os.path.join(project_dir, "settings.gradle"), "w") as f:
|
| 154 |
+
f.write(content)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def create_gradle_properties(project_dir):
|
| 158 |
+
content = """org.gradle.jvmargs=-Xmx3072m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
| 159 |
+
org.gradle.daemon=false
|
| 160 |
+
org.gradle.parallel=true
|
| 161 |
+
org.gradle.caching=true
|
| 162 |
+
android.useAndroidX=true
|
| 163 |
+
android.enableJetifier=true
|
| 164 |
+
"""
|
| 165 |
+
with open(os.path.join(project_dir, "gradle.properties"), "w") as f:
|
| 166 |
+
f.write(content)
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
def create_gradle_wrapper(project_dir):
|
| 170 |
+
wrapper_dir = os.path.join(project_dir, "gradle", "wrapper")
|
| 171 |
+
os.makedirs(wrapper_dir, exist_ok=True)
|
| 172 |
+
props_content = """distributionBase=GRADLE_USER_HOME
|
| 173 |
+
distributionPath=wrapper/dists
|
| 174 |
+
distributionUrl=https\\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
| 175 |
+
zipStoreBase=GRADLE_USER_HOME
|
| 176 |
+
zipStorePath=wrapper/dists
|
| 177 |
+
"""
|
| 178 |
+
with open(os.path.join(wrapper_dir, "gradle-wrapper.properties"), "w") as f:
|
| 179 |
+
f.write(props_content)
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
def create_app_build_gradle(app_dir, config):
|
| 183 |
+
package_name = config.get("package_name", "com.example.webapp")
|
| 184 |
+
version_name = config.get("version_name", "1.0.0")
|
| 185 |
+
version_code = config.get("version_code", "1")
|
| 186 |
+
minify_enabled = "true" if config.get("proguard_enabled") else "false"
|
| 187 |
+
|
| 188 |
+
# 🔥 Use the securely validated flag from config
|
| 189 |
+
use_firebase = config.get("actual_firebase_enabled", False)
|
| 190 |
+
|
| 191 |
+
use_onesignal = config.get("onesignal_enabled", False)
|
| 192 |
+
onesignal_id = config.get("onesignal_app_id", "")
|
| 193 |
+
use_admob = config.get("admob_enabled", False)
|
| 194 |
+
google_client_id = config.get("google_web_client_id", "")
|
| 195 |
+
|
| 196 |
+
dependencies = []
|
| 197 |
+
dependencies.append(" implementation 'androidx.appcompat:appcompat:1.6.1'")
|
| 198 |
+
dependencies.append(" implementation 'androidx.webkit:webkit:1.8.0'")
|
| 199 |
+
dependencies.append(" implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'")
|
| 200 |
+
dependencies.append(" implementation 'com.google.android.material:material:1.10.0'")
|
| 201 |
+
|
| 202 |
+
if google_client_id:
|
| 203 |
+
dependencies.append(" implementation 'com.google.android.gms:play-services-auth:20.7.0'")
|
| 204 |
+
|
| 205 |
+
if use_firebase:
|
| 206 |
+
dependencies.append(" implementation platform('com.google.firebase:firebase-bom:32.7.0')")
|
| 207 |
+
dependencies.append(" implementation 'com.google.firebase:firebase-analytics'")
|
| 208 |
+
dependencies.append(" implementation 'com.google.firebase:firebase-messaging'")
|
| 209 |
+
|
| 210 |
+
if use_admob:
|
| 211 |
+
dependencies.append(" implementation 'com.google.android.gms:play-services-ads:22.6.0'")
|
| 212 |
+
|
| 213 |
+
deps_str = "\n".join(dependencies)
|
| 214 |
+
|
| 215 |
+
firebase_plugin = ""
|
| 216 |
+
if use_firebase:
|
| 217 |
+
firebase_plugin = "apply plugin: 'com.google.gms.google-services'"
|
| 218 |
+
|
| 219 |
+
proguard_rules = ""
|
| 220 |
+
if config.get("proguard_enabled"):
|
| 221 |
+
proguard_rules = """ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'"""
|
| 222 |
+
|
| 223 |
+
content = """plugins {{
|
| 224 |
+
id 'com.android.application'
|
| 225 |
+
}}
|
| 226 |
+
{firebase_plugin}
|
| 227 |
+
|
| 228 |
+
android {{
|
| 229 |
+
namespace '{package_name}'
|
| 230 |
+
compileSdk 34
|
| 231 |
+
|
| 232 |
+
defaultConfig {{
|
| 233 |
+
applicationId "{package_name}"
|
| 234 |
+
minSdk 24
|
| 235 |
+
targetSdk 34
|
| 236 |
+
versionCode {version_code}
|
| 237 |
+
versionName "{version_name}"
|
| 238 |
+
}}
|
| 239 |
+
|
| 240 |
+
signingConfigs {{
|
| 241 |
+
release {{
|
| 242 |
+
storeFile file("../release-key.jks")
|
| 243 |
+
storePassword "android123"
|
| 244 |
+
keyAlias "releasekey"
|
| 245 |
+
keyPassword "android123"
|
| 246 |
+
}}
|
| 247 |
+
}}
|
| 248 |
+
|
| 249 |
+
buildTypes {{
|
| 250 |
+
release {{
|
| 251 |
+
minifyEnabled {minify_enabled}
|
| 252 |
+
shrinkResources {minify_enabled}
|
| 253 |
+
{proguard_rules}
|
| 254 |
+
signingConfig signingConfigs.release
|
| 255 |
+
}}
|
| 256 |
+
}}
|
| 257 |
+
|
| 258 |
+
compileOptions {{
|
| 259 |
+
sourceCompatibility JavaVersion.VERSION_17
|
| 260 |
+
targetCompatibility JavaVersion.VERSION_17
|
| 261 |
+
}}
|
| 262 |
+
|
| 263 |
+
lint {{
|
| 264 |
+
abortOnError false
|
| 265 |
+
checkReleaseBuilds false
|
| 266 |
+
}}
|
| 267 |
+
}}
|
| 268 |
+
|
| 269 |
+
dependencies {{
|
| 270 |
+
{deps_str}
|
| 271 |
+
}}
|
| 272 |
+
""".format(
|
| 273 |
+
firebase_plugin=firebase_plugin,
|
| 274 |
+
package_name=package_name,
|
| 275 |
+
version_code=version_code,
|
| 276 |
+
version_name=version_name,
|
| 277 |
+
minify_enabled=minify_enabled,
|
| 278 |
+
proguard_rules=proguard_rules,
|
| 279 |
+
deps_str=deps_str
|
| 280 |
+
)
|
| 281 |
+
with open(os.path.join(app_dir, "build.gradle"), "w") as f:
|
| 282 |
+
f.write(content)
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
def create_proguard_rules(app_dir, config):
|
| 286 |
+
rules = """-keepattributes Signature
|
| 287 |
+
-keepattributes *Annotation*
|
| 288 |
+
-keep class * extends android.app.Activity
|
| 289 |
+
-keep class * extends android.app.Application
|
| 290 |
+
-keep class * extends android.app.Service
|
| 291 |
+
-keep class * extends android.content.BroadcastReceiver
|
| 292 |
+
-keep class * extends android.content.ContentProvider
|
| 293 |
+
-dontwarn okhttp3.**
|
| 294 |
+
-dontwarn okio.**
|
| 295 |
+
-dontwarn javax.annotation.**
|
| 296 |
+
-keep class com.google.android.gms.** { *; }
|
| 297 |
+
-keep class com.google.firebase.** { *; }
|
| 298 |
+
-keep class com.google.android.gms.auth.api.signin.** { *; }
|
| 299 |
+
-keep class androidx.** { *; }
|
| 300 |
+
-keepclassmembers class * {
|
| 301 |
+
@android.webkit.JavascriptInterface <methods>;
|
| 302 |
+
}
|
| 303 |
+
"""
|
| 304 |
+
with open(os.path.join(app_dir, "proguard-rules.pro"), "w") as f:
|
| 305 |
+
f.write(rules)
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
def create_android_manifest(manifest_dir, config):
|
| 309 |
+
package_name = config.get("package_name", "com.example.webapp")
|
| 310 |
+
orientation = config.get("screen_orientation", "unspecified")
|
| 311 |
+
if orientation == "portrait":
|
| 312 |
+
orientation_str = "portrait"
|
| 313 |
+
elif orientation == "landscape":
|
| 314 |
+
orientation_str = "landscape"
|
| 315 |
+
else:
|
| 316 |
+
orientation_str = "unspecified"
|
| 317 |
+
|
| 318 |
+
permissions = []
|
| 319 |
+
permissions.append(' <uses-permission android:name="android.permission.INTERNET" />')
|
| 320 |
+
permissions.append(' <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />')
|
| 321 |
+
|
| 322 |
+
if config.get("perm_camera"):
|
| 323 |
+
permissions.append(' <uses-permission android:name="android.permission.CAMERA" />')
|
| 324 |
+
permissions.append(' <uses-feature android:name="android.hardware.camera" android:required="false" />')
|
| 325 |
+
if config.get("perm_microphone"):
|
| 326 |
+
permissions.append(' <uses-permission android:name="android.permission.RECORD_AUDIO" />')
|
| 327 |
+
if config.get("perm_location"):
|
| 328 |
+
permissions.append(' <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />')
|
| 329 |
+
permissions.append(' <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />')
|
| 330 |
+
if config.get("perm_storage"):
|
| 331 |
+
permissions.append(' <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />')
|
| 332 |
+
permissions.append(' <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />')
|
| 333 |
+
if config.get("perm_vibrate"):
|
| 334 |
+
permissions.append(' <uses-permission android:name="android.permission.VIBRATE" />')
|
| 335 |
+
if config.get("perm_contacts"):
|
| 336 |
+
permissions.append(' <uses-permission android:name="android.permission.READ_CONTACTS" />')
|
| 337 |
+
if config.get("perm_notifications"):
|
| 338 |
+
permissions.append(' <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />')
|
| 339 |
+
|
| 340 |
+
permissions_str = "\n".join(permissions)
|
| 341 |
+
|
| 342 |
+
fullscreen_theme = ""
|
| 343 |
+
if config.get("fullscreen_mode"):
|
| 344 |
+
fullscreen_theme = 'android:theme="@style/Theme.AppCompat.NoActionBar"'
|
| 345 |
+
else:
|
| 346 |
+
fullscreen_theme = 'android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"'
|
| 347 |
+
|
| 348 |
+
splash_activity = ""
|
| 349 |
+
main_activity_intent = ""
|
| 350 |
+
if config.get("splash_enabled"):
|
| 351 |
+
splash_activity = """
|
| 352 |
+
<activity
|
| 353 |
+
android:name=".SplashActivity"
|
| 354 |
+
android:exported="true"
|
| 355 |
+
android:screenOrientation="{orientation}"
|
| 356 |
+
{fullscreen_theme_attr}>
|
| 357 |
+
<intent-filter>
|
| 358 |
+
<action android:name="android.intent.action.MAIN" />
|
| 359 |
+
<category android:name="android.intent.category.LAUNCHER" />
|
| 360 |
+
</intent-filter>
|
| 361 |
+
</activity>""".format(orientation=orientation_str, fullscreen_theme_attr=fullscreen_theme)
|
| 362 |
+
main_activity_intent = ""
|
| 363 |
+
else:
|
| 364 |
+
main_activity_intent = """
|
| 365 |
+
<intent-filter>
|
| 366 |
+
<action android:name="android.intent.action.MAIN" />
|
| 367 |
+
<category android:name="android.intent.category.LAUNCHER" />
|
| 368 |
+
</intent-filter>"""
|
| 369 |
+
|
| 370 |
+
meta_data = ""
|
| 371 |
+
if config.get("admob_enabled"):
|
| 372 |
+
meta_data += """
|
| 373 |
+
<meta-data
|
| 374 |
+
android:name="com.google.android.gms.ads.APPLICATION_ID"
|
| 375 |
+
android:value="ca-app-pub-3940256099942544~3347511713" />"""
|
| 376 |
+
|
| 377 |
+
content = """<?xml version="1.0" encoding="utf-8"?>
|
| 378 |
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
| 379 |
+
|
| 380 |
+
{permissions}
|
| 381 |
+
|
| 382 |
+
<application
|
| 383 |
+
android:allowBackup="true"
|
| 384 |
+
android:icon="@mipmap/ic_launcher"
|
| 385 |
+
android:label="{app_name}"
|
| 386 |
+
android:supportsRtl="true"
|
| 387 |
+
android:usesCleartextTraffic="false"
|
| 388 |
+
android:hardwareAccelerated="true"
|
| 389 |
+
{fullscreen_theme}>
|
| 390 |
+
{splash_activity}
|
| 391 |
+
<activity
|
| 392 |
+
android:name=".MainActivity"
|
| 393 |
+
android:exported="true"
|
| 394 |
+
android:screenOrientation="{orientation}"
|
| 395 |
+
android:configChanges="orientation|screenSize|keyboardHidden"
|
| 396 |
+
android:windowSoftInputMode="adjustResize">
|
| 397 |
+
{main_activity_intent}
|
| 398 |
+
</activity>
|
| 399 |
+
{meta_data}
|
| 400 |
+
</application>
|
| 401 |
+
|
| 402 |
+
</manifest>
|
| 403 |
+
""".format(
|
| 404 |
+
permissions=permissions_str,
|
| 405 |
+
app_name=config.get("app_name", "WebApp").replace('"', '\\"'),
|
| 406 |
+
fullscreen_theme=fullscreen_theme,
|
| 407 |
+
splash_activity=splash_activity,
|
| 408 |
+
orientation=orientation_str,
|
| 409 |
+
main_activity_intent=main_activity_intent,
|
| 410 |
+
meta_data=meta_data
|
| 411 |
+
)
|
| 412 |
+
with open(os.path.join(manifest_dir, "AndroidManifest.xml"), "w") as f:
|
| 413 |
+
f.write(content)
|
| 414 |
+
|
| 415 |
+
|
| 416 |
+
def create_main_activity(java_dir, config):
|
| 417 |
+
package_name = config.get("package_name", "com.example.webapp")
|
| 418 |
+
target_url = config.get("target_url", "https://www.google.com")
|
| 419 |
+
enable_js = config.get("enable_javascript", True)
|
| 420 |
+
enable_dom = config.get("enable_dom_storage", True)
|
| 421 |
+
allow_file_access = config.get("allow_file_access", False)
|
| 422 |
+
media_autoplay = config.get("media_autoplay", False)
|
| 423 |
+
enable_zoom = config.get("enable_zoom", False)
|
| 424 |
+
custom_ua = config.get("custom_user_agent", "")
|
| 425 |
+
pull_to_refresh = config.get("pull_to_refresh", False)
|
| 426 |
+
multi_windows = config.get("multi_windows", False)
|
| 427 |
+
deep_link_intercept = config.get("deep_link_intercept", True)
|
| 428 |
+
prevent_screenshots = config.get("prevent_screenshots", False)
|
| 429 |
+
clear_cache_exit = config.get("clear_cache_exit", False)
|
| 430 |
+
clear_cookies_exit = config.get("clear_cookies_exit", False)
|
| 431 |
+
keep_screen_on = config.get("keep_screen_on", False)
|
| 432 |
+
fullscreen_mode = config.get("fullscreen_mode", False)
|
| 433 |
+
enforce_https = config.get("enforce_https", True)
|
| 434 |
+
root_detection = config.get("root_detection", False)
|
| 435 |
+
tamper_protection = config.get("tamper_protection", False)
|
| 436 |
+
status_bar_color = config.get("status_bar_color", "#1a1a2e")
|
| 437 |
+
bottom_nav = config.get("bottom_nav_enabled", False)
|
| 438 |
+
offline_mode = config.get("offline_mode", True)
|
| 439 |
+
mixed_content = "WebSettings.MIXED_CONTENT_NEVER_ALLOW" if enforce_https else "WebSettings.MIXED_CONTENT_ALWAYS_ALLOW"
|
| 440 |
+
perm_camera = config.get("perm_camera", False)
|
| 441 |
+
perm_microphone = config.get("perm_microphone", False)
|
| 442 |
+
perm_location = config.get("perm_location", False)
|
| 443 |
+
perm_storage = config.get("perm_storage", False)
|
| 444 |
+
perm_notifications = config.get("perm_notifications", False)
|
| 445 |
+
use_admob = config.get("admob_enabled", False)
|
| 446 |
+
admob_banner_id = config.get("admob_banner_id", "")
|
| 447 |
+
admob_interstitial_id = config.get("admob_interstitial_id", "")
|
| 448 |
+
|
| 449 |
+
# Dynamic variables
|
| 450 |
+
supabase_url = config.get("supabase_url", "")
|
| 451 |
+
supabase_anon_key = config.get("supabase_anon_key", "")
|
| 452 |
+
google_client_id = config.get("google_web_client_id", "")
|
| 453 |
+
|
| 454 |
+
imports = []
|
| 455 |
+
imports.append("package {};".format(package_name))
|
| 456 |
+
imports.append("")
|
| 457 |
+
imports.append("import android.app.Activity;")
|
| 458 |
+
imports.append("import android.content.Context;")
|
| 459 |
+
imports.append("import android.content.Intent;")
|
| 460 |
+
imports.append("import android.content.pm.PackageManager;")
|
| 461 |
+
imports.append("import android.graphics.Bitmap;")
|
| 462 |
+
imports.append("import android.graphics.Color;")
|
| 463 |
+
imports.append("import android.net.ConnectivityManager;")
|
| 464 |
+
imports.append("import android.net.NetworkInfo;")
|
| 465 |
+
imports.append("import android.net.Uri;")
|
| 466 |
+
imports.append("import android.os.Build;")
|
| 467 |
+
imports.append("import android.os.Bundle;")
|
| 468 |
+
imports.append("import android.view.View;")
|
| 469 |
+
imports.append("import android.view.Window;")
|
| 470 |
+
imports.append("import android.view.WindowManager;")
|
| 471 |
+
imports.append("import android.webkit.CookieManager;")
|
| 472 |
+
imports.append("import android.webkit.GeolocationPermissions;")
|
| 473 |
+
imports.append("import android.webkit.PermissionRequest;")
|
| 474 |
+
imports.append("import android.webkit.ValueCallback;")
|
| 475 |
+
imports.append("import android.webkit.WebChromeClient;")
|
| 476 |
+
imports.append("import android.webkit.WebResourceRequest;")
|
| 477 |
+
imports.append("import android.webkit.WebSettings;")
|
| 478 |
+
imports.append("import android.webkit.WebView;")
|
| 479 |
+
imports.append("import android.webkit.WebViewClient;")
|
| 480 |
+
imports.append("import android.widget.FrameLayout;")
|
| 481 |
+
imports.append("import android.widget.LinearLayout;")
|
| 482 |
+
imports.append("import android.widget.ImageButton;")
|
| 483 |
+
imports.append("import android.widget.ProgressBar;")
|
| 484 |
+
imports.append("import android.widget.Toast;")
|
| 485 |
+
imports.append("import androidx.appcompat.app.AppCompatActivity;")
|
| 486 |
+
imports.append("import androidx.core.app.ActivityCompat;")
|
| 487 |
+
imports.append("import androidx.core.content.ContextCompat;")
|
| 488 |
+
if pull_to_refresh:
|
| 489 |
+
imports.append("import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;")
|
| 490 |
+
if use_admob:
|
| 491 |
+
imports.append("import com.google.android.gms.ads.AdRequest;")
|
| 492 |
+
imports.append("import com.google.android.gms.ads.AdSize;")
|
| 493 |
+
imports.append("import com.google.android.gms.ads.AdView;")
|
| 494 |
+
imports.append("import com.google.android.gms.ads.MobileAds;")
|
| 495 |
+
imports.append("import com.google.android.gms.ads.interstitial.InterstitialAd;")
|
| 496 |
+
imports.append("import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback;")
|
| 497 |
+
imports.append("import com.google.android.gms.ads.LoadAdError;")
|
| 498 |
+
|
| 499 |
+
# Google Auth Imports
|
| 500 |
+
if google_client_id:
|
| 501 |
+
imports.append("import com.google.android.gms.auth.api.signin.GoogleSignIn;")
|
| 502 |
+
imports.append("import com.google.android.gms.auth.api.signin.GoogleSignInAccount;")
|
| 503 |
+
imports.append("import com.google.android.gms.auth.api.signin.GoogleSignInClient;")
|
| 504 |
+
imports.append("import com.google.android.gms.auth.api.signin.GoogleSignInOptions;")
|
| 505 |
+
imports.append("import com.google.android.gms.common.api.ApiException;")
|
| 506 |
+
imports.append("import com.google.android.gms.tasks.Task;")
|
| 507 |
+
imports.append("import android.webkit.JavascriptInterface;")
|
| 508 |
+
|
| 509 |
+
imports.append("import java.io.File;")
|
| 510 |
+
imports.append("import java.util.ArrayList;")
|
| 511 |
+
imports.append("import java.util.List;")
|
| 512 |
+
imports.append("")
|
| 513 |
+
|
| 514 |
+
runtime_perms_list = []
|
| 515 |
+
if perm_camera:
|
| 516 |
+
runtime_perms_list.append('"android.permission.CAMERA"')
|
| 517 |
+
if perm_microphone:
|
| 518 |
+
runtime_perms_list.append('"android.permission.RECORD_AUDIO"')
|
| 519 |
+
if perm_location:
|
| 520 |
+
runtime_perms_list.append('"android.permission.ACCESS_FINE_LOCATION"')
|
| 521 |
+
runtime_perms_list.append('"android.permission.ACCESS_COARSE_LOCATION"')
|
| 522 |
+
if perm_storage:
|
| 523 |
+
runtime_perms_list.append('"android.permission.READ_EXTERNAL_STORAGE"')
|
| 524 |
+
if perm_notifications:
|
| 525 |
+
runtime_perms_list.append('"android.permission.POST_NOTIFICATIONS"')
|
| 526 |
+
|
| 527 |
+
perms_array = ", ".join(runtime_perms_list)
|
| 528 |
+
|
| 529 |
+
# Google Auth Declarations
|
| 530 |
+
google_auth_decl = ""
|
| 531 |
+
if google_client_id:
|
| 532 |
+
google_auth_decl = """ private GoogleSignInClient mGoogleSignInClient;
|
| 533 |
+
private static final int RC_SIGN_IN = 9001;"""
|
| 534 |
+
|
| 535 |
+
root_detection_code = ""
|
| 536 |
+
if root_detection:
|
| 537 |
+
root_detection_code = """
|
| 538 |
+
if (isRootedOrEmulator()) {
|
| 539 |
+
Toast.makeText(this, "This app cannot run on rooted/emulated devices.", Toast.LENGTH_LONG).show();
|
| 540 |
+
finish();
|
| 541 |
+
return;
|
| 542 |
+
}"""
|
| 543 |
+
|
| 544 |
+
root_detection_method = ""
|
| 545 |
+
if root_detection:
|
| 546 |
+
root_detection_method = """
|
| 547 |
+
private boolean isRootedOrEmulator() {
|
| 548 |
+
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"};
|
| 549 |
+
for (String path : rootPaths) {
|
| 550 |
+
if (new File(path).exists()) return true;
|
| 551 |
+
}
|
| 552 |
+
String buildTags = android.os.Build.TAGS;
|
| 553 |
+
if (buildTags != null && buildTags.contains("test-keys")) return true;
|
| 554 |
+
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;
|
| 555 |
+
return false;
|
| 556 |
+
}"""
|
| 557 |
+
|
| 558 |
+
tamper_method = ""
|
| 559 |
+
if tamper_protection:
|
| 560 |
+
tamper_method = """
|
| 561 |
+
private boolean verifyAppSignature() {
|
| 562 |
+
try {
|
| 563 |
+
android.content.pm.PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), android.content.pm.PackageManager.GET_SIGNATURES);
|
| 564 |
+
for (android.content.pm.Signature signature : pInfo.signatures) {
|
| 565 |
+
java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA");
|
| 566 |
+
md.update(signature.toByteArray());
|
| 567 |
+
String currentSignature = android.util.Base64.encodeToString(md.digest(), android.util.Base64.DEFAULT).trim();
|
| 568 |
+
if (currentSignature != null && currentSignature.length() > 0) {
|
| 569 |
+
return true;
|
| 570 |
+
}
|
| 571 |
+
}
|
| 572 |
+
} catch (Exception e) {
|
| 573 |
+
e.printStackTrace();
|
| 574 |
+
}
|
| 575 |
+
return false;
|
| 576 |
+
}"""
|
| 577 |
+
|
| 578 |
+
tamper_check = ""
|
| 579 |
+
if tamper_protection:
|
| 580 |
+
tamper_check = """
|
| 581 |
+
if (!verifyAppSignature()) {
|
| 582 |
+
Toast.makeText(this, "App integrity check failed.", Toast.LENGTH_LONG).show();
|
| 583 |
+
finish();
|
| 584 |
+
return;
|
| 585 |
+
}"""
|
| 586 |
+
|
| 587 |
+
screenshot_code = ""
|
| 588 |
+
if prevent_screenshots:
|
| 589 |
+
screenshot_code = " getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);"
|
| 590 |
+
|
| 591 |
+
keep_screen_code = ""
|
| 592 |
+
if keep_screen_on:
|
| 593 |
+
keep_screen_code = " getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);"
|
| 594 |
+
|
| 595 |
+
fullscreen_code = ""
|
| 596 |
+
if fullscreen_mode:
|
| 597 |
+
fullscreen_code = """
|
| 598 |
+
getWindow().getDecorView().setSystemUiVisibility(
|
| 599 |
+
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
| 600 |
+
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
| 601 |
+
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
| 602 |
+
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
| 603 |
+
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
| 604 |
+
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
|
| 605 |
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
| 606 |
+
getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
| 607 |
+
}"""
|
| 608 |
+
|
| 609 |
+
status_bar_code = """
|
| 610 |
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {{
|
| 611 |
+
Window window = getWindow();
|
| 612 |
+
window.setStatusBarColor(Color.parseColor("{color}"));
|
| 613 |
+
}}""".format(color=status_bar_color if status_bar_color else "#1a1a2e")
|
| 614 |
+
|
| 615 |
+
swipe_refresh_setup = ""
|
| 616 |
+
swipe_refresh_decl = ""
|
| 617 |
+
if pull_to_refresh:
|
| 618 |
+
swipe_refresh_decl = " private SwipeRefreshLayout swipeRefreshLayout;"
|
| 619 |
+
swipe_refresh_setup = """
|
| 620 |
+
swipeRefreshLayout = new SwipeRefreshLayout(this);
|
| 621 |
+
swipeRefreshLayout.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
|
| 622 |
+
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
|
| 623 |
+
@Override
|
| 624 |
+
public void onRefresh() {
|
| 625 |
+
webView.reload();
|
| 626 |
+
}
|
| 627 |
+
});"""
|
| 628 |
+
|
| 629 |
+
ua_code = ""
|
| 630 |
+
if custom_ua:
|
| 631 |
+
ua_code = ' settings.setUserAgentString("{}");'.format(custom_ua.replace('"', '\\"'))
|
| 632 |
+
|
| 633 |
+
admob_decl = ""
|
| 634 |
+
admob_setup = ""
|
| 635 |
+
admob_interstitial_setup = ""
|
| 636 |
+
admob_interstitial_decl = ""
|
| 637 |
+
if use_admob:
|
| 638 |
+
admob_decl = " private AdView adView;"
|
| 639 |
+
admob_interstitial_decl = " private InterstitialAd mInterstitialAd;"
|
| 640 |
+
admob_setup = """
|
| 641 |
+
MobileAds.initialize(this, initializationStatus -> {{}});
|
| 642 |
+
adView = new AdView(this);
|
| 643 |
+
adView.setAdSize(AdSize.BANNER);
|
| 644 |
+
adView.setAdUnitId("{banner_id}");
|
| 645 |
+
AdRequest adRequest = new AdRequest.Builder().build();
|
| 646 |
+
adView.loadAd(adRequest);""".format(banner_id=admob_banner_id if admob_banner_id else "ca-app-pub-3940256099942544/6300978111")
|
| 647 |
+
if admob_interstitial_id:
|
| 648 |
+
admob_interstitial_setup = """
|
| 649 |
+
loadInterstitialAd();"""
|
| 650 |
+
|
| 651 |
+
admob_interstitial_method = ""
|
| 652 |
+
if use_admob and admob_interstitial_id:
|
| 653 |
+
admob_interstitial_method = """
|
| 654 |
+
private void loadInterstitialAd() {{
|
| 655 |
+
AdRequest adRequest = new AdRequest.Builder().build();
|
| 656 |
+
InterstitialAd.load(this, "{interstitial_id}", adRequest, new InterstitialAdLoadCallback() {{
|
| 657 |
+
@Override
|
| 658 |
+
public void onAdLoaded(InterstitialAd interstitialAd) {{
|
| 659 |
+
mInterstitialAd = interstitialAd;
|
| 660 |
+
}}
|
| 661 |
+
@Override
|
| 662 |
+
public void onAdFailedToLoad(LoadAdError loadAdError) {{
|
| 663 |
+
mInterstitialAd = null;
|
| 664 |
+
}}
|
| 665 |
+
}});
|
| 666 |
+
}}
|
| 667 |
+
|
| 668 |
+
private void showInterstitialAd() {{
|
| 669 |
+
if (mInterstitialAd != null) {{
|
| 670 |
+
mInterstitialAd.show(this);
|
| 671 |
+
loadInterstitialAd();
|
| 672 |
+
}}
|
| 673 |
+
}}""".format(interstitial_id=admob_interstitial_id)
|
| 674 |
+
|
| 675 |
+
deep_link_code = ""
|
| 676 |
+
if deep_link_intercept:
|
| 677 |
+
deep_link_code = """
|
| 678 |
+
String host = Uri.parse(url).getHost();
|
| 679 |
+
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"))) {
|
| 680 |
+
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
| 681 |
+
startActivity(intent);
|
| 682 |
+
return true;
|
| 683 |
+
}"""
|
| 684 |
+
|
| 685 |
+
offline_check_code = ""
|
| 686 |
+
if offline_mode:
|
| 687 |
+
offline_check_code = """
|
| 688 |
+
if (!isNetworkAvailable()) {
|
| 689 |
+
webView.loadUrl("file:///android_asset/offline.html");
|
| 690 |
+
} else {
|
| 691 |
+
webView.loadUrl(TARGET_URL);
|
| 692 |
+
}"""
|
| 693 |
+
else:
|
| 694 |
+
offline_check_code = """
|
| 695 |
+
webView.loadUrl(TARGET_URL);"""
|
| 696 |
+
|
| 697 |
+
offline_method = ""
|
| 698 |
+
if offline_mode:
|
| 699 |
+
offline_method = """
|
| 700 |
+
private boolean isNetworkAvailable() {
|
| 701 |
+
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
| 702 |
+
if (cm != null) {
|
| 703 |
+
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
|
| 704 |
+
return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
|
| 705 |
+
}
|
| 706 |
+
return false;
|
| 707 |
+
}"""
|
| 708 |
+
|
| 709 |
+
on_destroy_code = ""
|
| 710 |
+
destroy_lines = []
|
| 711 |
+
if clear_cache_exit:
|
| 712 |
+
destroy_lines.append(" if (webView != null) { webView.clearCache(true); }")
|
| 713 |
+
if clear_cookies_exit:
|
| 714 |
+
destroy_lines.append(" CookieManager.getInstance().removeAllCookies(null);")
|
| 715 |
+
destroy_lines.append(" CookieManager.getInstance().flush();")
|
| 716 |
+
if destroy_lines:
|
| 717 |
+
on_destroy_code = """
|
| 718 |
+
@Override
|
| 719 |
+
protected void onDestroy() {{
|
| 720 |
+
super.onDestroy();
|
| 721 |
+
{lines}
|
| 722 |
+
}}""".format(lines="\n".join(destroy_lines))
|
| 723 |
+
|
| 724 |
+
request_perms_code = ""
|
| 725 |
+
if runtime_perms_list:
|
| 726 |
+
request_perms_code = """
|
| 727 |
+
List<String> permissionsNeeded = new ArrayList<>();
|
| 728 |
+
String[] allPerms = new String[]{{{perms}}};
|
| 729 |
+
for (String perm : allPerms) {{
|
| 730 |
+
if (ContextCompat.checkSelfPermission(this, perm) != PackageManager.PERMISSION_GRANTED) {{
|
| 731 |
+
permissionsNeeded.add(perm);
|
| 732 |
+
}}
|
| 733 |
+
}}
|
| 734 |
+
if (!permissionsNeeded.isEmpty()) {{
|
| 735 |
+
ActivityCompat.requestPermissions(this, permissionsNeeded.toArray(new String[0]), 1001);
|
| 736 |
+
}}""".format(perms=perms_array)
|
| 737 |
+
|
| 738 |
+
bottom_nav_code_layout = ""
|
| 739 |
+
bottom_nav_code_setup = ""
|
| 740 |
+
if bottom_nav:
|
| 741 |
+
bottom_nav_code_layout = """
|
| 742 |
+
LinearLayout bottomNav = new LinearLayout(this);
|
| 743 |
+
bottomNav.setOrientation(LinearLayout.HORIZONTAL);
|
| 744 |
+
bottomNav.setBackgroundColor(Color.parseColor("#1a1a2e"));
|
| 745 |
+
bottomNav.setGravity(android.view.Gravity.CENTER);
|
| 746 |
+
LinearLayout.LayoutParams navParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 130);
|
| 747 |
+
bottomNav.setLayoutParams(navParams);
|
| 748 |
+
|
| 749 |
+
ImageButton btnBack = new ImageButton(this);
|
| 750 |
+
btnBack.setImageResource(android.R.drawable.ic_media_previous);
|
| 751 |
+
btnBack.setBackgroundColor(Color.TRANSPARENT);
|
| 752 |
+
btnBack.setColorFilter(Color.WHITE);
|
| 753 |
+
LinearLayout.LayoutParams btnParams = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1);
|
| 754 |
+
btnBack.setLayoutParams(btnParams);
|
| 755 |
+
btnBack.setOnClickListener(v -> { if (webView.canGoBack()) webView.goBack(); });
|
| 756 |
+
|
| 757 |
+
ImageButton btnHome = new ImageButton(this);
|
| 758 |
+
btnHome.setImageResource(android.R.drawable.ic_menu_revert);
|
| 759 |
+
btnHome.setBackgroundColor(Color.TRANSPARENT);
|
| 760 |
+
btnHome.setColorFilter(Color.WHITE);
|
| 761 |
+
btnHome.setLayoutParams(btnParams);
|
| 762 |
+
btnHome.setOnClickListener(v -> webView.loadUrl(TARGET_URL));
|
| 763 |
+
|
| 764 |
+
ImageButton btnRefresh = new ImageButton(this);
|
| 765 |
+
btnRefresh.setImageResource(android.R.drawable.ic_popup_sync);
|
| 766 |
+
btnRefresh.setBackgroundColor(Color.TRANSPARENT);
|
| 767 |
+
btnRefresh.setColorFilter(Color.WHITE);
|
| 768 |
+
btnRefresh.setLayoutParams(btnParams);
|
| 769 |
+
btnRefresh.setOnClickListener(v -> webView.reload());
|
| 770 |
+
|
| 771 |
+
bottomNav.addView(btnBack);
|
| 772 |
+
bottomNav.addView(btnHome);
|
| 773 |
+
bottomNav.addView(btnRefresh);"""
|
| 774 |
+
|
| 775 |
+
build_layout = ""
|
| 776 |
+
if pull_to_refresh and bottom_nav and use_admob:
|
| 777 |
+
build_layout = """
|
| 778 |
+
LinearLayout rootLayout = new LinearLayout(this);
|
| 779 |
+
rootLayout.setOrientation(LinearLayout.VERTICAL);
|
| 780 |
+
rootLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
|
| 781 |
+
|
| 782 |
+
FrameLayout.LayoutParams webParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, 0);
|
| 783 |
+
LinearLayout.LayoutParams webLinearParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1);
|
| 784 |
+
|
| 785 |
+
swipeRefreshLayout.addView(webView);
|
| 786 |
+
swipeRefreshLayout.setLayoutParams(webLinearParams);
|
| 787 |
+
rootLayout.addView(swipeRefreshLayout);
|
| 788 |
+
rootLayout.addView(adView);
|
| 789 |
+
{bottom_nav_layout}
|
| 790 |
+
rootLayout.addView(bottomNav);
|
| 791 |
+
setContentView(rootLayout);""".format(bottom_nav_layout=bottom_nav_code_layout)
|
| 792 |
+
elif pull_to_refresh and bottom_nav:
|
| 793 |
+
build_layout = """
|
| 794 |
+
LinearLayout rootLayout = new LinearLayout(this);
|
| 795 |
+
rootLayout.setOrientation(LinearLayout.VERTICAL);
|
| 796 |
+
rootLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
|
| 797 |
+
|
| 798 |
+
LinearLayout.LayoutParams webLinearParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1);
|
| 799 |
+
|
| 800 |
+
swipeRefreshLayout.addView(webView);
|
| 801 |
+
swipeRefreshLayout.setLayoutParams(webLinearParams);
|
| 802 |
+
rootLayout.addView(swipeRefreshLayout);
|
| 803 |
+
{bottom_nav_layout}
|
| 804 |
+
rootLayout.addView(bottomNav);
|
| 805 |
+
setContentView(rootLayout);""".format(bottom_nav_layout=bottom_nav_code_layout)
|
| 806 |
+
elif pull_to_refresh and use_admob:
|
| 807 |
+
build_layout = """
|
| 808 |
+
LinearLayout rootLayout = new LinearLayout(this);
|
| 809 |
+
rootLayout.setOrientation(LinearLayout.VERTICAL);
|
| 810 |
+
rootLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
|
| 811 |
+
|
| 812 |
+
LinearLayout.LayoutParams webLinearParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1);
|
| 813 |
+
|
| 814 |
+
swipeRefreshLayout.addView(webView);
|
| 815 |
+
swipeRefreshLayout.setLayoutParams(webLinearParams);
|
| 816 |
+
rootLayout.addView(swipeRefreshLayout);
|
| 817 |
+
rootLayout.addView(adView);
|
| 818 |
+
setContentView(rootLayout);"""
|
| 819 |
+
elif bottom_nav and use_admob:
|
| 820 |
+
build_layout = """
|
| 821 |
+
LinearLayout rootLayout = new LinearLayout(this);
|
| 822 |
+
rootLayout.setOrientation(LinearLayout.VERTICAL);
|
| 823 |
+
rootLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
|
| 824 |
+
|
| 825 |
+
LinearLayout.LayoutParams webLinearParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1);
|
| 826 |
+
webView.setLayoutParams(webLinearParams);
|
| 827 |
+
rootLayout.addView(webView);
|
| 828 |
+
rootLayout.addView(adView);
|
| 829 |
+
{bottom_nav_layout}
|
| 830 |
+
rootLayout.addView(bottomNav);
|
| 831 |
+
setContentView(rootLayout);""".format(bottom_nav_layout=bottom_nav_code_layout)
|
| 832 |
+
elif pull_to_refresh:
|
| 833 |
+
build_layout = """
|
| 834 |
+
swipeRefreshLayout.addView(webView);
|
| 835 |
+
setContentView(swipeRefreshLayout);"""
|
| 836 |
+
elif bottom_nav:
|
| 837 |
+
build_layout = """
|
| 838 |
+
LinearLayout rootLayout = new LinearLayout(this);
|
| 839 |
+
rootLayout.setOrientation(LinearLayout.VERTICAL);
|
| 840 |
+
rootLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
|
| 841 |
+
|
| 842 |
+
LinearLayout.LayoutParams webLinearParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1);
|
| 843 |
+
webView.setLayoutParams(webLinearParams);
|
| 844 |
+
rootLayout.addView(webView);
|
| 845 |
+
{bottom_nav_layout}
|
| 846 |
+
rootLayout.addView(bottomNav);
|
| 847 |
+
setContentView(rootLayout);""".format(bottom_nav_layout=bottom_nav_code_layout)
|
| 848 |
+
elif use_admob:
|
| 849 |
+
build_layout = """
|
| 850 |
+
LinearLayout rootLayout = new LinearLayout(this);
|
| 851 |
+
rootLayout.setOrientation(LinearLayout.VERTICAL);
|
| 852 |
+
rootLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
|
| 853 |
+
|
| 854 |
+
LinearLayout.LayoutParams webLinearParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1);
|
| 855 |
+
webView.setLayoutParams(webLinearParams);
|
| 856 |
+
rootLayout.addView(webView);
|
| 857 |
+
rootLayout.addView(adView);
|
| 858 |
+
setContentView(rootLayout);"""
|
| 859 |
+
else:
|
| 860 |
+
build_layout = """
|
| 861 |
+
setContentView(webView);"""
|
| 862 |
+
|
| 863 |
+
# Variable Injection Scripts
|
| 864 |
+
key_injection = ""
|
| 865 |
+
if supabase_url or supabase_anon_key:
|
| 866 |
+
key_injection = """
|
| 867 |
+
view.evaluateJavascript("window.SUPABASE_URL = '{url}'; window.SUPABASE_ANON_KEY = '{key}';", null);""".format(
|
| 868 |
+
url=supabase_url.replace("'", "\\'"), key=supabase_anon_key.replace("'", "\\'")
|
| 869 |
+
)
|
| 870 |
+
|
| 871 |
+
swipe_page_finished = ""
|
| 872 |
+
if pull_to_refresh:
|
| 873 |
+
swipe_page_finished = """
|
| 874 |
+
if (swipeRefreshLayout != null) {
|
| 875 |
+
swipeRefreshLayout.setRefreshing(false);
|
| 876 |
+
}"""
|
| 877 |
+
|
| 878 |
+
# Google Auth Bridge Setup
|
| 879 |
+
google_auth_setup = ""
|
| 880 |
+
google_auth_methods = ""
|
| 881 |
+
google_auth_result = ""
|
| 882 |
+
if google_client_id:
|
| 883 |
+
google_auth_setup = """
|
| 884 |
+
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
|
| 885 |
+
.requestIdToken("{client_id}")
|
| 886 |
+
.requestEmail()
|
| 887 |
+
.build();
|
| 888 |
+
mGoogleSignInClient = GoogleSignIn.getClient(this, gso);
|
| 889 |
+
webView.addJavascriptInterface(new AndroidBridge(), "AndroidBridge");
|
| 890 |
+
""".format(client_id=google_client_id)
|
| 891 |
+
|
| 892 |
+
google_auth_methods = """
|
| 893 |
+
public class AndroidBridge {
|
| 894 |
+
@JavascriptInterface
|
| 895 |
+
public void loginWithGoogle() {
|
| 896 |
+
Intent signInIntent = mGoogleSignInClient.getSignInIntent();
|
| 897 |
+
startActivityForResult(signInIntent, RC_SIGN_IN);
|
| 898 |
+
}
|
| 899 |
+
}
|
| 900 |
+
|
| 901 |
+
private void handleSignInResult(Task<GoogleSignInAccount> completedTask) {
|
| 902 |
+
try {
|
| 903 |
+
GoogleSignInAccount account = completedTask.getResult(ApiException.class);
|
| 904 |
+
if (account != null) {
|
| 905 |
+
String idToken = account.getIdToken();
|
| 906 |
+
webView.post(() -> webView.evaluateJavascript("if(typeof onNativeLoginSuccess === 'function') { onNativeLoginSuccess('" + idToken + "'); }", null));
|
| 907 |
+
}
|
| 908 |
+
} catch (ApiException e) {
|
| 909 |
+
e.printStackTrace();
|
| 910 |
+
webView.post(() -> webView.evaluateJavascript("if(typeof onNativeLoginError === 'function') { onNativeLoginError('" + e.getStatusCode() + "'); }", null));
|
| 911 |
+
}
|
| 912 |
+
}"""
|
| 913 |
+
|
| 914 |
+
google_auth_result = """
|
| 915 |
+
if (requestCode == RC_SIGN_IN) {
|
| 916 |
+
Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
|
| 917 |
+
handleSignInResult(task);
|
| 918 |
+
return;
|
| 919 |
+
}"""
|
| 920 |
+
|
| 921 |
+
content = """{imports_str}
|
| 922 |
+
public class MainActivity extends AppCompatActivity {{
|
| 923 |
+
|
| 924 |
+
private WebView webView;
|
| 925 |
+
private static final String TARGET_URL = "{target_url}";
|
| 926 |
+
private ValueCallback<Uri[]> fileUploadCallback;
|
| 927 |
+
private static final int FILE_CHOOSER_RESULT = 1002;
|
| 928 |
+
{swipe_decl}
|
| 929 |
+
{admob_d}
|
| 930 |
+
{admob_interstitial_d}
|
| 931 |
+
{google_auth_decl_str}
|
| 932 |
+
|
| 933 |
+
@Override
|
| 934 |
+
protected void onCreate(Bundle savedInstanceState) {{
|
| 935 |
+
super.onCreate(savedInstanceState);
|
| 936 |
+
{screenshot_c}
|
| 937 |
+
{keep_screen_c}
|
| 938 |
+
{fullscreen_c}
|
| 939 |
+
{status_bar_c}
|
| 940 |
+
{root_det_c}
|
| 941 |
+
{tamper_c}
|
| 942 |
+
|
| 943 |
+
webView = new WebView(this);
|
| 944 |
+
webView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
|
| 945 |
+
webView.setBackgroundColor(Color.parseColor("#1a1a2e"));
|
| 946 |
+
|
| 947 |
+
WebSettings settings = webView.getSettings();
|
| 948 |
+
settings.setJavaScriptEnabled({js_enabled});
|
| 949 |
+
settings.setDomStorageEnabled({dom_enabled});
|
| 950 |
+
settings.setAllowFileAccess({file_access});
|
| 951 |
+
settings.setMediaPlaybackRequiresUserGesture({media_gesture});
|
| 952 |
+
settings.setSupportZoom({zoom_enabled});
|
| 953 |
+
settings.setBuiltInZoomControls({zoom_enabled});
|
| 954 |
+
settings.setDisplayZoomControls(false);
|
| 955 |
+
settings.setJavaScriptCanOpenWindowsAutomatically({multi_win});
|
| 956 |
+
settings.setSupportMultipleWindows({multi_win});
|
| 957 |
+
settings.setMixedContentMode({mixed_content});
|
| 958 |
+
settings.setLoadWithOverviewMode(true);
|
| 959 |
+
settings.setUseWideViewPort(true);
|
| 960 |
+
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
|
| 961 |
+
settings.setDatabaseEnabled(true);
|
| 962 |
+
settings.setAllowContentAccess(true);
|
| 963 |
+
|
| 964 |
+
// Security Enhancements
|
| 965 |
+
settings.setGeolocationEnabled(true);
|
| 966 |
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {{
|
| 967 |
+
settings.setSafeBrowsingEnabled(true);
|
| 968 |
+
}}
|
| 969 |
+
{ua_c}
|
| 970 |
+
|
| 971 |
+
CookieManager cookieManager = CookieManager.getInstance();
|
| 972 |
+
cookieManager.setAcceptCookie(true);
|
| 973 |
+
cookieManager.setAcceptThirdPartyCookies(webView, true);
|
| 974 |
+
|
| 975 |
+
{swipe_setup}
|
| 976 |
+
{admob_s}
|
| 977 |
+
{admob_inter_s}
|
| 978 |
+
{request_perms}
|
| 979 |
+
{google_auth_setup_str}
|
| 980 |
+
|
| 981 |
+
webView.setWebViewClient(new WebViewClient() {{
|
| 982 |
+
@Override
|
| 983 |
+
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {{
|
| 984 |
+
String url = request.getUrl().toString();
|
| 985 |
+
{deep_link}
|
| 986 |
+
if (url.startsWith("tel:") || url.startsWith("mailto:") || url.startsWith("sms:") || url.startsWith("whatsapp:") || url.startsWith("intent:")) {{
|
| 987 |
+
try {{
|
| 988 |
+
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
| 989 |
+
startActivity(intent);
|
| 990 |
+
}} catch (Exception e) {{
|
| 991 |
+
e.printStackTrace();
|
| 992 |
+
}}
|
| 993 |
+
return true;
|
| 994 |
+
}}
|
| 995 |
+
return false;
|
| 996 |
+
}}
|
| 997 |
+
|
| 998 |
+
@Override
|
| 999 |
+
public void onPageStarted(WebView view, String url, Bitmap favicon) {{
|
| 1000 |
+
super.onPageStarted(view, url, favicon);
|
| 1001 |
+
}}
|
| 1002 |
+
|
| 1003 |
+
@Override
|
| 1004 |
+
public void onPageFinished(WebView view, String url) {{
|
| 1005 |
+
super.onPageFinished(view, url);
|
| 1006 |
+
{swipe_finished}
|
| 1007 |
+
{key_injection_str}
|
| 1008 |
+
}}
|
| 1009 |
+
|
| 1010 |
+
@Override
|
| 1011 |
+
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {{
|
| 1012 |
+
super.onReceivedError(view, errorCode, description, failingUrl);
|
| 1013 |
+
{offline_on_error}
|
| 1014 |
+
}}
|
| 1015 |
+
}});
|
| 1016 |
+
|
| 1017 |
+
webView.setWebChromeClient(new WebChromeClient() {{
|
| 1018 |
+
@Override
|
| 1019 |
+
public void onPermissionRequest(final PermissionRequest request) {{
|
| 1020 |
+
runOnUiThread(() -> request.grant(request.getResources()));
|
| 1021 |
+
}}
|
| 1022 |
+
|
| 1023 |
+
@Override
|
| 1024 |
+
public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {{
|
| 1025 |
+
callback.invoke(origin, true, false);
|
| 1026 |
+
}}
|
| 1027 |
+
|
| 1028 |
+
@Override
|
| 1029 |
+
public boolean onShowFileChooser(WebView wv, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {{
|
| 1030 |
+
if (fileUploadCallback != null) {{
|
| 1031 |
+
fileUploadCallback.onReceiveValue(null);
|
| 1032 |
+
}}
|
| 1033 |
+
fileUploadCallback = filePathCallback;
|
| 1034 |
+
Intent intent = fileChooserParams.createIntent();
|
| 1035 |
+
try {{
|
| 1036 |
+
startActivityForResult(intent, FILE_CHOOSER_RESULT);
|
| 1037 |
+
}} catch (Exception e) {{
|
| 1038 |
+
fileUploadCallback = null;
|
| 1039 |
+
return false;
|
| 1040 |
+
}}
|
| 1041 |
+
return true;
|
| 1042 |
+
}}
|
| 1043 |
+
}});
|
| 1044 |
+
|
| 1045 |
+
{build_layout_code}
|
| 1046 |
+
|
| 1047 |
+
{offline_check}
|
| 1048 |
+
}}
|
| 1049 |
+
|
| 1050 |
+
@Override
|
| 1051 |
+
protected void onActivityResult(int requestCode, int resultCode, Intent data) {{
|
| 1052 |
+
super.onActivityResult(requestCode, resultCode, data);
|
| 1053 |
+
{google_auth_res_str}
|
| 1054 |
+
if (requestCode == FILE_CHOOSER_RESULT) {{
|
| 1055 |
+
if (fileUploadCallback != null) {{
|
| 1056 |
+
Uri[] results = null;
|
| 1057 |
+
if (resultCode == Activity.RESULT_OK && data != null) {{
|
| 1058 |
+
String dataString = data.getDataString();
|
| 1059 |
+
if (dataString != null) {{
|
| 1060 |
+
results = new Uri[]{{Uri.parse(dataString)}};
|
| 1061 |
+
}}
|
| 1062 |
+
}}
|
| 1063 |
+
fileUploadCallback.onReceiveValue(results);
|
| 1064 |
+
fileUploadCallback = null;
|
| 1065 |
+
}}
|
| 1066 |
+
}}
|
| 1067 |
+
}}
|
| 1068 |
+
|
| 1069 |
+
@Override
|
| 1070 |
+
public void onBackPressed() {{
|
| 1071 |
+
if (webView.canGoBack()) {{
|
| 1072 |
+
webView.goBack();
|
| 1073 |
+
}} else {{
|
| 1074 |
+
super.onBackPressed();
|
| 1075 |
+
}}
|
| 1076 |
+
}}
|
| 1077 |
+
{offline_method_code}
|
| 1078 |
+
{root_method}
|
| 1079 |
+
{tamper_method_code}
|
| 1080 |
+
{admob_inter_method}
|
| 1081 |
+
{google_auth_methods_str}
|
| 1082 |
+
{on_destroy}
|
| 1083 |
+
}}
|
| 1084 |
+
""".format(
|
| 1085 |
+
imports_str="\n".join(imports),
|
| 1086 |
+
target_url=target_url.replace('"', '\\"'),
|
| 1087 |
+
swipe_decl=swipe_refresh_decl,
|
| 1088 |
+
admob_d=admob_decl,
|
| 1089 |
+
admob_interstitial_d=admob_interstitial_decl,
|
| 1090 |
+
google_auth_decl_str=google_auth_decl,
|
| 1091 |
+
screenshot_c=screenshot_code,
|
| 1092 |
+
keep_screen_c=keep_screen_code,
|
| 1093 |
+
fullscreen_c=fullscreen_code,
|
| 1094 |
+
status_bar_c=status_bar_code,
|
| 1095 |
+
root_det_c=root_detection_code,
|
| 1096 |
+
tamper_c=tamper_check,
|
| 1097 |
+
js_enabled="true" if enable_js else "false",
|
| 1098 |
+
dom_enabled="true" if enable_dom else "false",
|
| 1099 |
+
file_access="true" if allow_file_access else "false",
|
| 1100 |
+
media_gesture="false" if media_autoplay else "true",
|
| 1101 |
+
zoom_enabled="true" if enable_zoom else "false",
|
| 1102 |
+
multi_win="true" if multi_windows else "false",
|
| 1103 |
+
mixed_content=mixed_content,
|
| 1104 |
+
ua_c=ua_code,
|
| 1105 |
+
swipe_setup=swipe_refresh_setup,
|
| 1106 |
+
admob_s=admob_setup,
|
| 1107 |
+
admob_inter_s=admob_interstitial_setup,
|
| 1108 |
+
request_perms=request_perms_code,
|
| 1109 |
+
google_auth_setup_str=google_auth_setup,
|
| 1110 |
+
deep_link=deep_link_code,
|
| 1111 |
+
swipe_finished=swipe_page_finished,
|
| 1112 |
+
key_injection_str=key_injection,
|
| 1113 |
+
offline_on_error=' if (!isNetworkAvailable()) { view.loadUrl("file:///android_asset/offline.html"); }' if offline_mode else "",
|
| 1114 |
+
build_layout_code=build_layout,
|
| 1115 |
+
offline_check=offline_check_code,
|
| 1116 |
+
offline_method_code=offline_method,
|
| 1117 |
+
root_method=root_detection_method,
|
| 1118 |
+
tamper_method_code=tamper_method,
|
| 1119 |
+
admob_inter_method=admob_interstitial_method,
|
| 1120 |
+
google_auth_methods_str=google_auth_methods,
|
| 1121 |
+
google_auth_res_str=google_auth_result,
|
| 1122 |
+
on_destroy=on_destroy_code
|
| 1123 |
+
)
|
| 1124 |
+
|
| 1125 |
+
with open(os.path.join(java_dir, "MainActivity.java"), "w") as f:
|
| 1126 |
+
f.write(content)
|
| 1127 |
+
|
| 1128 |
+
|
| 1129 |
+
def create_splash_activity(java_dir, config):
|
| 1130 |
+
package_name = config.get("package_name", "com.example.webapp")
|
| 1131 |
+
splash_text = config.get("splash_text", "Loading...")
|
| 1132 |
+
splash_text_color = config.get("splash_text_color", "#FFFFFF")
|
| 1133 |
+
splash_bg_color = config.get("splash_bg_color", "#1a1a2e")
|
| 1134 |
+
has_splash_image = config.get("has_splash_image", False)
|
| 1135 |
+
|
| 1136 |
+
image_code = ""
|
| 1137 |
+
if has_splash_image:
|
| 1138 |
+
image_code = """
|
| 1139 |
+
ImageView splashImage = new ImageView(this);
|
| 1140 |
+
try {
|
| 1141 |
+
java.io.InputStream is = getAssets().open("splash_media");
|
| 1142 |
+
android.graphics.drawable.Drawable d = android.graphics.drawable.Drawable.createFromStream(is, null);
|
| 1143 |
+
splashImage.setImageDrawable(d);
|
| 1144 |
+
splashImage.setScaleType(ImageView.ScaleType.FIT_CENTER);
|
| 1145 |
+
LinearLayout.LayoutParams imgParams = new LinearLayout.LayoutParams(600, 600);
|
| 1146 |
+
imgParams.gravity = android.view.Gravity.CENTER;
|
| 1147 |
+
splashImage.setLayoutParams(imgParams);
|
| 1148 |
+
layout.addView(splashImage);
|
| 1149 |
+
is.close();
|
| 1150 |
+
} catch (Exception e) {
|
| 1151 |
+
e.printStackTrace();
|
| 1152 |
+
}"""
|
| 1153 |
+
|
| 1154 |
+
content = """package {package_name};
|
| 1155 |
+
|
| 1156 |
+
import android.content.Intent;
|
| 1157 |
+
import android.graphics.Color;
|
| 1158 |
+
import android.os.Bundle;
|
| 1159 |
+
import android.os.Handler;
|
| 1160 |
+
import android.view.Gravity;
|
| 1161 |
+
import android.view.WindowManager;
|
| 1162 |
+
import android.widget.ImageView;
|
| 1163 |
+
import android.widget.LinearLayout;
|
| 1164 |
+
import android.widget.TextView;
|
| 1165 |
+
import androidx.appcompat.app.AppCompatActivity;
|
| 1166 |
+
|
| 1167 |
+
public class SplashActivity extends AppCompatActivity {{
|
| 1168 |
+
|
| 1169 |
+
@Override
|
| 1170 |
+
protected void onCreate(Bundle savedInstanceState) {{
|
| 1171 |
+
super.onCreate(savedInstanceState);
|
| 1172 |
+
|
| 1173 |
+
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
| 1174 |
+
|
| 1175 |
+
LinearLayout layout = new LinearLayout(this);
|
| 1176 |
+
layout.setOrientation(LinearLayout.VERTICAL);
|
| 1177 |
+
layout.setGravity(Gravity.CENTER);
|
| 1178 |
+
layout.setBackgroundColor(Color.parseColor("{bg_color}"));
|
| 1179 |
+
layout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
|
| 1180 |
+
{image_code}
|
| 1181 |
+
|
| 1182 |
+
TextView textView = new TextView(this);
|
| 1183 |
+
textView.setText("{splash_text}");
|
| 1184 |
+
textView.setTextColor(Color.parseColor("{text_color}"));
|
| 1185 |
+
textView.setTextSize(24);
|
| 1186 |
+
textView.setGravity(Gravity.CENTER);
|
| 1187 |
+
LinearLayout.LayoutParams textParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
| 1188 |
+
textParams.topMargin = 40;
|
| 1189 |
+
textView.setLayoutParams(textParams);
|
| 1190 |
+
layout.addView(textView);
|
| 1191 |
+
|
| 1192 |
+
setContentView(layout);
|
| 1193 |
+
|
| 1194 |
+
new Handler().postDelayed(new Runnable() {{
|
| 1195 |
+
@Override
|
| 1196 |
+
public void run() {{
|
| 1197 |
+
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
|
| 1198 |
+
startActivity(intent);
|
| 1199 |
+
finish();
|
| 1200 |
+
}}
|
| 1201 |
+
}}, 3000);
|
| 1202 |
+
}}
|
| 1203 |
+
}}
|
| 1204 |
+
""".format(
|
| 1205 |
+
package_name=package_name,
|
| 1206 |
+
bg_color=splash_bg_color if splash_bg_color else "#1a1a2e",
|
| 1207 |
+
image_code=image_code,
|
| 1208 |
+
splash_text=splash_text.replace('"', '\\"') if splash_text else "Loading...",
|
| 1209 |
+
text_color=splash_text_color if splash_text_color else "#FFFFFF"
|
| 1210 |
+
)
|
| 1211 |
+
|
| 1212 |
+
with open(os.path.join(java_dir, "SplashActivity.java"), "w") as f:
|
| 1213 |
+
f.write(content)
|
| 1214 |
+
|
| 1215 |
+
|
| 1216 |
+
def save_app_icon(res_dir, icon_b64):
|
| 1217 |
+
sizes = {
|
| 1218 |
+
"mipmap-mdpi": 48,
|
| 1219 |
+
"mipmap-hdpi": 72,
|
| 1220 |
+
"mipmap-xhdpi": 96,
|
| 1221 |
+
"mipmap-xxhdpi": 144,
|
| 1222 |
+
"mipmap-xxxhdpi": 192
|
| 1223 |
+
}
|
| 1224 |
+
try:
|
| 1225 |
+
if "," in icon_b64:
|
| 1226 |
+
icon_b64 = icon_b64.split(",")[1]
|
| 1227 |
+
icon_bytes = base64.b64decode(icon_b64)
|
| 1228 |
+
for folder, size in sizes.items():
|
| 1229 |
+
mipmap_dir = os.path.join(res_dir, folder)
|
| 1230 |
+
os.makedirs(mipmap_dir, exist_ok=True)
|
| 1231 |
+
icon_path = os.path.join(mipmap_dir, "ic_launcher.png")
|
| 1232 |
+
with open(icon_path, "wb") as f:
|
| 1233 |
+
f.write(icon_bytes)
|
| 1234 |
+
except Exception as e:
|
| 1235 |
+
for folder, size in sizes.items():
|
| 1236 |
+
mipmap_dir = os.path.join(res_dir, folder)
|
| 1237 |
+
os.makedirs(mipmap_dir, exist_ok=True)
|
| 1238 |
+
create_default_icon(os.path.join(mipmap_dir, "ic_launcher.png"))
|
| 1239 |
+
|
| 1240 |
+
|
| 1241 |
+
def create_default_icon(path):
|
| 1242 |
+
import struct
|
| 1243 |
+
import zlib
|
| 1244 |
+
|
| 1245 |
+
width = 48
|
| 1246 |
+
height = 48
|
| 1247 |
+
|
| 1248 |
+
def create_png(w, h, r, g, b):
|
| 1249 |
+
def chunk(chunk_type, data):
|
| 1250 |
+
c = chunk_type + data
|
| 1251 |
+
crc = zlib.crc32(c) & 0xFFFFFFFF
|
| 1252 |
+
return struct.pack(">I", len(data)) + c + struct.pack(">I", crc)
|
| 1253 |
+
|
| 1254 |
+
header = b'\x89PNG\r\n\x1a\n'
|
| 1255 |
+
ihdr = chunk(b'IHDR', struct.pack(">IIBBBBB", w, h, 8, 2, 0, 0, 0))
|
| 1256 |
+
raw_data = b''
|
| 1257 |
+
for y in range(h):
|
| 1258 |
+
raw_data += b'\x00'
|
| 1259 |
+
for x in range(w):
|
| 1260 |
+
raw_data += bytes([r, g, b])
|
| 1261 |
+
compressed = zlib.compress(raw_data)
|
| 1262 |
+
idat = chunk(b'IDAT', compressed)
|
| 1263 |
+
iend = chunk(b'IEND', b'')
|
| 1264 |
+
return header + ihdr + idat + iend
|
| 1265 |
+
|
| 1266 |
+
png_data = create_png(width, height, 0x4e, 0x54, 0xc8)
|
| 1267 |
+
with open(path, "wb") as f:
|
| 1268 |
+
f.write(png_data)
|
| 1269 |
+
|
| 1270 |
+
|
| 1271 |
+
def setup_project(build_id, config):
|
| 1272 |
+
project_dir = os.path.join(BUILD_DIR_BASE, build_id, "project")
|
| 1273 |
+
os.makedirs(project_dir, exist_ok=True)
|
| 1274 |
+
|
| 1275 |
+
package_name = config.get("package_name", "com.example.webapp")
|
| 1276 |
+
package_path = package_name.replace(".", "/")
|
| 1277 |
+
|
| 1278 |
+
app_dir = os.path.join(project_dir, "app")
|
| 1279 |
+
src_main = os.path.join(app_dir, "src", "main")
|
| 1280 |
+
java_dir = os.path.join(src_main, "java", package_path)
|
| 1281 |
+
res_dir = os.path.join(src_main, "res")
|
| 1282 |
+
assets_dir = os.path.join(src_main, "assets")
|
| 1283 |
+
|
| 1284 |
+
os.makedirs(java_dir, exist_ok=True)
|
| 1285 |
+
os.makedirs(res_dir, exist_ok=True)
|
| 1286 |
+
os.makedirs(assets_dir, exist_ok=True)
|
| 1287 |
+
os.makedirs(os.path.join(res_dir, "values"), exist_ok=True)
|
| 1288 |
+
|
| 1289 |
+
# 🔥 FIREBASE VALIDATION LOGIC 🔥
|
| 1290 |
+
use_firebase = config.get("firebase_enabled", False)
|
| 1291 |
+
use_onesignal = config.get("onesignal_enabled", False)
|
| 1292 |
+
firebase_json = config.get("firebase_json", "")
|
| 1293 |
+
|
| 1294 |
+
actual_firebase_enabled = False
|
| 1295 |
+
|
| 1296 |
+
if use_firebase and firebase_json.strip():
|
| 1297 |
+
try:
|
| 1298 |
+
# Check if it's valid JSON AND contains the crucial "project_info" key
|
| 1299 |
+
parsed = json.loads(firebase_json)
|
| 1300 |
+
if "project_info" in parsed:
|
| 1301 |
+
# Only write the file and enable the plugin if it's perfectly valid
|
| 1302 |
+
os.makedirs(app_dir, exist_ok=True)
|
| 1303 |
+
with open(os.path.join(app_dir, "google-services.json"), "w") as f:
|
| 1304 |
+
json.dump(parsed, f, indent=2)
|
| 1305 |
+
actual_firebase_enabled = True
|
| 1306 |
+
else:
|
| 1307 |
+
LOG_STORE[build_id].append("⚠️ WARNING: Firebase JSON is missing 'project_info'. Firebase integration skipped to prevent build failure.")
|
| 1308 |
+
except json.JSONDecodeError:
|
| 1309 |
+
LOG_STORE[build_id].append("⚠️ WARNING: Firebase JSON is invalid. Firebase integration skipped to prevent build failure.")
|
| 1310 |
+
elif use_firebase:
|
| 1311 |
+
LOG_STORE[build_id].append("⚠️ WARNING: Firebase was enabled but no JSON was provided. Integration skipped.")
|
| 1312 |
+
|
| 1313 |
+
# Save actual state to config so create_app_build_gradle knows what to do
|
| 1314 |
+
config["actual_firebase_enabled"] = actual_firebase_enabled
|
| 1315 |
+
|
| 1316 |
+
create_build_gradle_root(project_dir, actual_firebase_enabled, use_onesignal)
|
| 1317 |
+
create_settings_gradle(project_dir, config.get("app_name", "WebApp"))
|
| 1318 |
+
create_gradle_properties(project_dir)
|
| 1319 |
+
create_gradle_wrapper(project_dir)
|
| 1320 |
+
|
| 1321 |
+
create_app_build_gradle(app_dir, config)
|
| 1322 |
+
create_proguard_rules(app_dir, config)
|
| 1323 |
+
|
| 1324 |
+
create_android_manifest(src_main, config)
|
| 1325 |
+
|
| 1326 |
+
create_main_activity(java_dir, config)
|
| 1327 |
+
|
| 1328 |
+
if config.get("splash_enabled"):
|
| 1329 |
+
create_splash_activity(java_dir, config)
|
| 1330 |
+
|
| 1331 |
+
icon_b64 = config.get("app_icon_b64", "")
|
| 1332 |
+
if icon_b64:
|
| 1333 |
+
save_app_icon(res_dir, icon_b64)
|
| 1334 |
+
else:
|
| 1335 |
+
for folder in ["mipmap-mdpi", "mipmap-hdpi", "mipmap-xhdpi", "mipmap-xxhdpi", "mipmap-xxxhdpi"]:
|
| 1336 |
+
mipmap_dir = os.path.join(res_dir, folder)
|
| 1337 |
+
os.makedirs(mipmap_dir, exist_ok=True)
|
| 1338 |
+
create_default_icon(os.path.join(mipmap_dir, "ic_launcher.png"))
|
| 1339 |
+
|
| 1340 |
+
splash_media_b64 = config.get("splash_media_b64", "")
|
| 1341 |
+
if splash_media_b64 and config.get("splash_enabled"):
|
| 1342 |
+
try:
|
| 1343 |
+
if "," in splash_media_b64:
|
| 1344 |
+
splash_media_b64 = splash_media_b64.split(",")[1]
|
| 1345 |
+
media_bytes = base64.b64decode(splash_media_b64)
|
| 1346 |
+
with open(os.path.join(assets_dir, "splash_media"), "wb") as f:
|
| 1347 |
+
f.write(media_bytes)
|
| 1348 |
+
config["has_splash_image"] = True
|
| 1349 |
+
create_splash_activity(java_dir, config)
|
| 1350 |
+
except Exception as e:
|
| 1351 |
+
config["has_splash_image"] = False
|
| 1352 |
+
|
| 1353 |
+
if config.get("offline_mode", True):
|
| 1354 |
+
with open(os.path.join(assets_dir, "offline.html"), "w") as f:
|
| 1355 |
+
f.write(generate_offline_html())
|
| 1356 |
+
|
| 1357 |
+
strings_xml = """<?xml version="1.0" encoding="utf-8"?>
|
| 1358 |
+
<resources>
|
| 1359 |
+
<string name="app_name">{app_name}</string>
|
| 1360 |
+
</resources>
|
| 1361 |
+
""".format(app_name=config.get("app_name", "WebApp").replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """))
|
| 1362 |
+
with open(os.path.join(res_dir, "values", "strings.xml"), "w") as f:
|
| 1363 |
+
f.write(strings_xml)
|
| 1364 |
+
|
| 1365 |
+
colors_xml = """<?xml version="1.0" encoding="utf-8"?>
|
| 1366 |
+
<resources>
|
| 1367 |
+
<color name="colorPrimary">{status_color}</color>
|
| 1368 |
+
<color name="colorPrimaryDark">{status_color}</color>
|
| 1369 |
+
<color name="colorAccent">#e94560</color>
|
| 1370 |
+
</resources>
|
| 1371 |
+
""".format(status_color=config.get("status_bar_color", "#1a1a2e"))
|
| 1372 |
+
with open(os.path.join(res_dir, "values", "colors.xml"), "w") as f:
|
| 1373 |
+
f.write(colors_xml)
|
| 1374 |
+
|
| 1375 |
+
styles_xml = """<?xml version="1.0" encoding="utf-8"?>
|
| 1376 |
+
<resources>
|
| 1377 |
+
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
|
| 1378 |
+
<item name="colorPrimary">@color/colorPrimary</item>
|
| 1379 |
+
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
| 1380 |
+
<item name="colorAccent">@color/colorAccent</item>
|
| 1381 |
+
</style>
|
| 1382 |
+
</resources>
|
| 1383 |
+
"""
|
| 1384 |
+
with open(os.path.join(res_dir, "values", "styles.xml"), "w") as f:
|
| 1385 |
+
f.write(styles_xml)
|
| 1386 |
+
|
| 1387 |
+
generate_keystore(os.path.join(BUILD_DIR_BASE, build_id, "project"), config.get("dev_name", "Developer"), config.get("dev_email", "dev@example.com"), build_id)
|
| 1388 |
+
|
| 1389 |
+
local_props = "sdk.dir={}\n".format(os.environ.get("ANDROID_SDK_ROOT", "/opt/android-sdk"))
|
| 1390 |
+
with open(os.path.join(project_dir, "local.properties"), "w") as f:
|
| 1391 |
+
f.write(local_props)
|
| 1392 |
+
|
| 1393 |
+
return project_dir
|
| 1394 |
+
|
| 1395 |
+
|
| 1396 |
+
def run_build(build_id, config):
|
| 1397 |
+
LOG_STORE[build_id] = []
|
| 1398 |
+
BUILD_STATUS[build_id] = "running"
|
| 1399 |
+
|
| 1400 |
+
def log(msg):
|
| 1401 |
+
timestamp = datetime.now().strftime("%H:%M:%S")
|
| 1402 |
+
LOG_STORE[build_id].append("[{}] {}".format(timestamp, msg))
|
| 1403 |
+
|
| 1404 |
+
try:
|
| 1405 |
+
log("=== Build Started ===")
|
| 1406 |
+
log("App Name: {}".format(config.get("app_name", "WebApp")))
|
| 1407 |
+
log("Package: {}".format(config.get("package_name", "com.example.webapp")))
|
| 1408 |
+
log("Build Format: {}".format(config.get("build_format", "apk").upper()))
|
| 1409 |
+
log("Setting up project structure...")
|
| 1410 |
+
|
| 1411 |
+
project_dir = setup_project(build_id, config)
|
| 1412 |
+
|
| 1413 |
+
log("Project structure created successfully.")
|
| 1414 |
+
log("Starting Gradle build...")
|
| 1415 |
+
|
| 1416 |
+
build_format = config.get("build_format", "apk").lower()
|
| 1417 |
+
if build_format == "aab":
|
| 1418 |
+
gradle_task = "bundleRelease"
|
| 1419 |
+
else:
|
| 1420 |
+
gradle_task = "assembleRelease"
|
| 1421 |
+
|
| 1422 |
+
gradle_bin = os.path.join(os.environ.get("GRADLE_HOME", "/opt/gradle/gradle-7.6"), "bin", "gradle")
|
| 1423 |
+
|
| 1424 |
+
cmd = [
|
| 1425 |
+
gradle_bin,
|
| 1426 |
+
gradle_task,
|
| 1427 |
+
"--parallel",
|
| 1428 |
+
"--build-cache",
|
| 1429 |
+
"--no-daemon",
|
| 1430 |
+
"--stacktrace"
|
| 1431 |
+
]
|
| 1432 |
+
|
| 1433 |
+
log("Running: {}".format(" ".join(cmd)))
|
| 1434 |
+
|
| 1435 |
+
env = os.environ.copy()
|
| 1436 |
+
env["ANDROID_SDK_ROOT"] = os.environ.get("ANDROID_SDK_ROOT", "/opt/android-sdk")
|
| 1437 |
+
env["ANDROID_HOME"] = os.environ.get("ANDROID_HOME", "/opt/android-sdk")
|
| 1438 |
+
env["JAVA_HOME"] = os.environ.get("JAVA_HOME", "/usr/lib/jvm/java-17-openjdk-amd64")
|
| 1439 |
+
|
| 1440 |
+
process = subprocess.Popen(
|
| 1441 |
+
cmd,
|
| 1442 |
+
cwd=project_dir,
|
| 1443 |
+
stdout=subprocess.PIPE,
|
| 1444 |
+
stderr=subprocess.STDOUT,
|
| 1445 |
+
text=True,
|
| 1446 |
+
env=env,
|
| 1447 |
+
bufsize=1
|
| 1448 |
+
)
|
| 1449 |
+
|
| 1450 |
+
for line in iter(process.stdout.readline, ""):
|
| 1451 |
+
stripped = line.rstrip()
|
| 1452 |
+
if stripped:
|
| 1453 |
+
log(stripped)
|
| 1454 |
+
|
| 1455 |
+
process.wait()
|
| 1456 |
+
|
| 1457 |
+
if process.returncode == 0:
|
| 1458 |
+
log("=== BUILD SUCCESSFUL ===")
|
| 1459 |
+
|
| 1460 |
+
if build_format == "aab":
|
| 1461 |
+
output_path = os.path.join(project_dir, "app", "build", "outputs", "bundle", "release", "app-release.aab")
|
| 1462 |
+
else:
|
| 1463 |
+
output_path = os.path.join(project_dir, "app", "build", "outputs", "apk", "release", "app-release.apk")
|
| 1464 |
+
if not os.path.exists(output_path):
|
| 1465 |
+
output_path = os.path.join(project_dir, "app", "build", "outputs", "apk", "release", "app-release-unsigned.apk")
|
| 1466 |
+
|
| 1467 |
+
if os.path.exists(output_path):
|
| 1468 |
+
artifact_name = "{}.{}".format(config.get("app_name", "app").replace(" ", "_"), "aab" if build_format == "aab" else "apk")
|
| 1469 |
+
final_path = os.path.join(BUILD_DIR_BASE, build_id, artifact_name)
|
| 1470 |
+
shutil.copy2(output_path, final_path)
|
| 1471 |
+
BUILD_ARTIFACTS[build_id] = final_path
|
| 1472 |
+
log("Artifact saved: {}".format(artifact_name))
|
| 1473 |
+
BUILD_STATUS[build_id] = "success"
|
| 1474 |
+
else:
|
| 1475 |
+
log("ERROR: Build output file not found at expected path.")
|
| 1476 |
+
log("Searching for output files...")
|
| 1477 |
+
for root, dirs, files in os.walk(os.path.join(project_dir, "app", "build", "outputs")):
|
| 1478 |
+
for fname in files:
|
| 1479 |
+
log(" Found: {}".format(os.path.join(root, fname)))
|
| 1480 |
+
BUILD_STATUS[build_id] = "failed"
|
| 1481 |
+
else:
|
| 1482 |
+
log("=== BUILD FAILED (exit code: {}) ===".format(process.returncode))
|
| 1483 |
+
BUILD_STATUS[build_id] = "failed"
|
| 1484 |
+
|
| 1485 |
+
full_log = "\n".join(LOG_STORE[build_id])
|
| 1486 |
+
ai_analysis = analyze_logs_ai(full_log)
|
| 1487 |
+
log("")
|
| 1488 |
+
log("=== AI Log Analysis (Arabic) ===")
|
| 1489 |
+
for analysis_line in ai_analysis.split("\n"):
|
| 1490 |
+
log(analysis_line)
|
| 1491 |
+
|
| 1492 |
+
except Exception as e:
|
| 1493 |
+
log("CRITICAL ERROR: {}".format(str(e)))
|
| 1494 |
+
import traceback
|
| 1495 |
+
for tb_line in traceback.format_exc().split("\n"):
|
| 1496 |
+
log(tb_line)
|
| 1497 |
+
BUILD_STATUS[build_id] = "failed"
|
| 1498 |
+
|
| 1499 |
+
|
| 1500 |
+
@app.route("/")
|
| 1501 |
+
def index():
|
| 1502 |
+
return render_template("index.html")
|
| 1503 |
+
|
| 1504 |
+
|
| 1505 |
+
@app.route("/build", methods=["POST"])
|
| 1506 |
+
def start_build():
|
| 1507 |
+
try:
|
| 1508 |
+
data = request.form.to_dict()
|
| 1509 |
+
|
| 1510 |
+
config = {}
|
| 1511 |
+
config["app_name"] = data.get("app_name", "MyApp")
|
| 1512 |
+
config["package_name"] = data.get("package_name", "com.example.myapp")
|
| 1513 |
+
config["target_url"] = data.get("target_url", "https://www.google.com")
|
| 1514 |
+
config["build_format"] = data.get("build_format", "apk")
|
| 1515 |
+
config["version_name"] = data.get("version_name", "1.0.0")
|
| 1516 |
+
config["version_code"] = data.get("version_code", "1")
|
| 1517 |
+
config["screen_orientation"] = data.get("screen_orientation", "unspecified")
|
| 1518 |
+
config["dev_name"] = data.get("dev_name", "Developer")
|
| 1519 |
+
config["dev_email"] = data.get("dev_email", "dev@example.com")
|
| 1520 |
+
|
| 1521 |
+
# Key injections variables
|
| 1522 |
+
config["google_web_client_id"] = data.get("google_web_client_id", "")
|
| 1523 |
+
config["supabase_url"] = data.get("supabase_url", "")
|
| 1524 |
+
config["supabase_anon_key"] = data.get("supabase_anon_key", "")
|
| 1525 |
+
|
| 1526 |
+
config["splash_enabled"] = data.get("splash_enabled") == "on"
|
| 1527 |
+
config["splash_text"] = data.get("splash_text", "Loading...")
|
| 1528 |
+
config["splash_text_color"] = data.get("splash_text_color", "#FFFFFF")
|
| 1529 |
+
config["splash_bg_color"] = data.get("splash_bg_color", "#1a1a2e")
|
| 1530 |
+
config["status_bar_color"] = data.get("status_bar_color", "#1a1a2e")
|
| 1531 |
+
config["bottom_nav_enabled"] = data.get("bottom_nav_enabled") == "on"
|
| 1532 |
+
|
| 1533 |
+
config["enable_javascript"] = data.get("enable_javascript") == "on"
|
| 1534 |
+
config["enable_dom_storage"] = data.get("enable_dom_storage") == "on"
|
| 1535 |
+
config["allow_file_access"] = data.get("allow_file_access") == "on"
|
| 1536 |
+
config["media_autoplay"] = data.get("media_autoplay") == "on"
|
| 1537 |
+
config["enable_zoom"] = data.get("enable_zoom") == "on"
|
| 1538 |
+
config["custom_user_agent"] = data.get("custom_user_agent", "")
|
| 1539 |
+
config["pull_to_refresh"] = data.get("pull_to_refresh") == "on"
|
| 1540 |
+
config["multi_windows"] = data.get("multi_windows") == "on"
|
| 1541 |
+
config["deep_link_intercept"] = data.get("deep_link_intercept") == "on"
|
| 1542 |
+
|
| 1543 |
+
config["proguard_enabled"] = data.get("proguard_enabled") == "on"
|
| 1544 |
+
config["root_detection"] = data.get("root_detection") == "on"
|
| 1545 |
+
config["prevent_screenshots"] = data.get("prevent_screenshots") == "on"
|
| 1546 |
+
config["clear_cache_exit"] = data.get("clear_cache_exit") == "on"
|
| 1547 |
+
config["clear_cookies_exit"] = data.get("clear_cookies_exit") == "on"
|
| 1548 |
+
config["keep_screen_on"] = data.get("keep_screen_on") == "on"
|
| 1549 |
+
config["fullscreen_mode"] = data.get("fullscreen_mode") == "on"
|
| 1550 |
+
config["enforce_https"] = data.get("enforce_https") == "on"
|
| 1551 |
+
config["tamper_protection"] = data.get("tamper_protection") == "on"
|
| 1552 |
+
|
| 1553 |
+
config["firebase_enabled"] = data.get("firebase_enabled") == "on"
|
| 1554 |
+
config["firebase_json"] = data.get("firebase_json", "")
|
| 1555 |
+
config["onesignal_enabled"] = bool(data.get("onesignal_app_id", "").strip())
|
| 1556 |
+
config["onesignal_app_id"] = data.get("onesignal_app_id", "")
|
| 1557 |
+
config["admob_enabled"] = data.get("admob_enabled") == "on"
|
| 1558 |
+
config["admob_banner_id"] = data.get("admob_banner_id", "")
|
| 1559 |
+
config["admob_interstitial_id"] = data.get("admob_interstitial_id", "")
|
| 1560 |
+
|
| 1561 |
+
config["perm_camera"] = data.get("perm_camera") == "on"
|
| 1562 |
+
config["perm_microphone"] = data.get("perm_microphone") == "on"
|
| 1563 |
+
config["perm_location"] = data.get("perm_location") == "on"
|
| 1564 |
+
config["perm_storage"] = data.get("perm_storage") == "on"
|
| 1565 |
+
config["perm_vibrate"] = data.get("perm_vibrate") == "on"
|
| 1566 |
+
config["perm_contacts"] = data.get("perm_contacts") == "on"
|
| 1567 |
+
config["perm_notifications"] = data.get("perm_notifications") == "on"
|
| 1568 |
+
|
| 1569 |
+
config["offline_mode"] = data.get("offline_mode") == "on"
|
| 1570 |
+
|
| 1571 |
+
config["app_icon_b64"] = data.get("app_icon_b64", "")
|
| 1572 |
+
config["splash_media_b64"] = data.get("splash_media_b64", "")
|
| 1573 |
+
config["has_splash_image"] = bool(data.get("splash_media_b64", "").strip())
|
| 1574 |
+
|
| 1575 |
+
build_id = str(uuid.uuid4())[:12]
|
| 1576 |
+
os.makedirs(os.path.join(BUILD_DIR_BASE, build_id), exist_ok=True)
|
| 1577 |
+
|
| 1578 |
+
thread = threading.Thread(target=run_build, args=(build_id, config), daemon=True)
|
| 1579 |
+
thread.start()
|
| 1580 |
+
|
| 1581 |
+
return jsonify({"status": "started", "build_id": build_id})
|
| 1582 |
+
except Exception as e:
|
| 1583 |
+
return jsonify({"status": "error", "message": str(e)}), 500
|
| 1584 |
+
|
| 1585 |
+
|
| 1586 |
+
@app.route("/logs/<build_id>")
|
| 1587 |
+
def get_logs(build_id):
|
| 1588 |
+
def generate():
|
| 1589 |
+
last_index = 0
|
| 1590 |
+
while True:
|
| 1591 |
+
logs = LOG_STORE.get(build_id, [])
|
| 1592 |
+
status = BUILD_STATUS.get(build_id, "unknown")
|
| 1593 |
+
if last_index < len(logs):
|
| 1594 |
+
new_logs = logs[last_index:]
|
| 1595 |
+
last_index = len(logs)
|
| 1596 |
+
for log_line in new_logs:
|
| 1597 |
+
yield "data: {}\n\n".format(json.dumps({"type": "log", "message": log_line}))
|
| 1598 |
+
if status in ("success", "failed"):
|
| 1599 |
+
yield "data: {}\n\n".format(json.dumps({"type": "status", "status": status, "build_id": build_id}))
|
| 1600 |
+
break
|
| 1601 |
+
time.sleep(0.5)
|
| 1602 |
+
|
| 1603 |
+
return Response(generate(), mimetype="text/event-stream", headers={
|
| 1604 |
+
"Cache-Control": "no-cache",
|
| 1605 |
+
"X-Accel-Buffering": "no",
|
| 1606 |
+
"Connection": "keep-alive"
|
| 1607 |
+
})
|
| 1608 |
+
|
| 1609 |
+
|
| 1610 |
+
@app.route("/download/<build_id>")
|
| 1611 |
+
def download_artifact(build_id):
|
| 1612 |
+
artifact_path = BUILD_ARTIFACTS.get(build_id)
|
| 1613 |
+
if artifact_path and os.path.exists(artifact_path):
|
| 1614 |
+
return send_file(artifact_path, as_attachment=True, download_name=os.path.basename(artifact_path))
|
| 1615 |
+
else:
|
| 1616 |
+
return jsonify({"error": "Artifact not found"}), 404
|
| 1617 |
+
|
| 1618 |
+
|
| 1619 |
+
@app.route("/status/<build_id>")
|
| 1620 |
+
def get_status(build_id):
|
| 1621 |
+
status = BUILD_STATUS.get(build_id, "unknown")
|
| 1622 |
+
return jsonify({"status": status, "build_id": build_id})
|
| 1623 |
+
|
| 1624 |
+
|
| 1625 |
+
if __name__ == "__main__":
|
| 1626 |
+
app.run(host="0.0.0.0", port=7860, debug=False, threaded=True)
|