Ttgg / templates /index.html
Athagi's picture
Create templates/index.html
bad8f36 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>APK Editor</title>
<style>
:root {
--primary-color: #4285f4;
--secondary-color: #34a853;
--danger-color: #ea4335;
--warning-color: #fbbc05;
--light-color: #f8f9fa;
--dark-color: #343a40;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background-color: #f5f5f5;
color: #333;
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: var(--primary-color);
color: white;
padding: 20px 0;
text-align: center;
margin-bottom: 30px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
h1 {
margin-bottom: 10px;
}
.card {
background-color: white;
border-radius: 5px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
padding: 20px;
margin-bottom: 20px;
}
.card h2 {
margin-bottom: 15px;
color: var(--primary-color);
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input[type="file"] {
display: none;
}
.file-upload {
border: 2px dashed #ccc;
padding: 20px;
text-align: center;
cursor: pointer;
margin-bottom: 10px;
border-radius: 5px;
transition: all 0.3s;
}
.file-upload:hover {
border-color: var(--primary-color);
background-color: #f8f9fa;
}
.btn {
display: inline-block;
background-color: var(--primary-color);
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
text-decoration: none;
font-size: 16px;
transition: background-color 0.3s;
}
.btn:hover {
background-color: #3367d6;
}
.btn-secondary {
background-color: var(--secondary-color);
}
.btn-secondary:hover {
background-color: #2d9249;
}
.btn-danger {
background-color: var(--danger-color);
}
.btn-danger:hover {
background-color: #d33426;
}
.progress-container {
width: 100%;
background-color: #f1f1f1;
border-radius: 5px;
margin: 10px 0;
display: none;
}
.progress-bar {
height: 20px;
background-color: var(--primary-color);
border-radius: 5px;
width: 0%;
transition: width 0.3s;
text-align: center;
color: white;
line-height: 20px;
font-size: 12px;
}
.status {
padding: 10px;
border-radius: 5px;
margin-bottom: 10px;
display: none;
}
.status.success {
background-color: #d4edda;
color: #155724;
display: block;
}
.status.error {
background-color: #f8d7da;
color: #721c24;
display: block;
}
.status.info {
background-color: #d1ecf1;
color: #0c5460;
display: block;
}
.icon-preview {
max-width: 100px;
max-height: 100px;
margin: 10px 0;
display: none;
}
.step {
margin-bottom: 30px;
}
.step-number {
display: inline-block;
background-color: var(--primary-color);
color: white;
width: 30px;
height: 30px;
text-align: center;
line-height: 30px;
border-radius: 50%;
margin-right: 10px;
}
.hidden {
display: none;
}
#downloadBtn {
display: none;
}
@media (max-width: 768px) {
.container {
padding: 10px;
}
.card {
padding: 15px;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>APK Editor</h1>
<p>Modify and customize your Android APK files</p>
</header>
<div class="card">
<div class="step">
<h2><span class="step-number">1</span>Upload APK</h2>
<div class="form-group">
<label for="apkFile">Select APK File:</label>
<input type="file" id="apkFile" accept=".apk">
<div class="file-upload" id="fileUpload">
<p>Click to select APK file or drag and drop here</p>
<p id="fileName">No file selected</p>
</div>
<button class="btn" id="uploadBtn">Upload</button>
</div>
<div class="progress-container" id="uploadProgressContainer">
<div class="progress-bar" id="uploadProgressBar">0%</div>
</div>
<div class="status" id="uploadStatus"></div>
</div>
<div class="step hidden" id="decompileStep">
<h2><span class="step-number">2</span>Decompile APK</h2>
<p>Decompile the APK to access its resources and manifest</p>
<button class="btn" id="decompileBtn">Decompile</button>
<div class="progress-container" id="decompileProgressContainer">
<div class="progress-bar" id="decompileProgressBar">0%</div>
</div>
<div class="status" id="decompileStatus"></div>
<img id="appIcon" class="icon-preview" alt="App Icon">
</div>
<div class="step hidden" id="modifyStep">
<h2><span class="step-number">3</span>Modify APK</h2>
<div class="form-group">
<label for="appName">New App Name:</label>
<input type="text" id="appName" class="form-control" placeholder="Enter new app name">
</div>
<div class="form-group">
<label for="packageName">New Package Name:</label>
<input type="text" id="packageName" class="form-control" placeholder="com.example.newapp">
</div>
<div class="form-group">
<label for="versionCode">Version Code:</label>
<input type="number" id="versionCode" class="form-control" placeholder="1">
</div>
<div class="form-group">
<label for="versionName">Version Name:</label>
<input type="text" id="versionName" class="form-control" placeholder="1.0">
</div>
<button class="btn" id="saveChangesBtn">Save Changes</button>
<div class="status" id="modifyStatus"></div>
</div>
<div class="step hidden" id="recompileStep">
<h2><span class="step-number">4</span>Recompile APK</h2>
<p>Recompile the modified APK</p>
<button class="btn" id="recompileBtn">Recompile</button>
<div class="progress-container" id="recompileProgressContainer">
<div class="progress-bar" id="recompileProgressBar">0%</div>
</div>
<div class="status" id="recompileStatus"></div>
</div>
<div class="step hidden" id="signStep">
<h2><span class="step-number">5</span>Sign APK</h2>
<p>Sign the APK with a debug key</p>
<button class="btn" id="signBtn">Sign APK</button>
<div class="progress-container" id="signProgressContainer">
<div class="progress-bar" id="signProgressBar">0%</div>
</div>
<div class="status" id="signStatus"></div>
</div>
<div class="step hidden" id="downloadStep">
<h2><span class="step-number">6</span>Download APK</h2>
<p>Your modified APK is ready!</p>
<a href="#" class="btn btn-secondary" id="downloadBtn">Download APK</a>
<div class="status" id="downloadStatus"></div>
</div>
</div>
</div>
<script>
// Global variables to store APK file info
let currentApk = {
path: null,
decompiledPath: null,
modifiedPath: null,
signedPath: null
};
// DOM elements
const apkFileInput = document.getElementById('apkFile');
const fileUpload = document.getElementById('fileUpload');
const fileName = document.getElementById('fileName');
const uploadBtn = document.getElementById('uploadBtn');
const uploadProgressContainer = document.getElementById('uploadProgressContainer');
const uploadProgressBar = document.getElementById('uploadProgressBar');
const uploadStatus = document.getElementById('uploadStatus');
const decompileStep = document.getElementById('decompileStep');
const decompileBtn = document.getElementById('decompileBtn');
const decompileProgressContainer = document.getElementById('decompileProgressContainer');
const decompileProgressBar = document.getElementById('decompileProgressBar');
const decompileStatus = document.getElementById('decompileStatus');
const appIcon = document.getElementById('appIcon');
const modifyStep = document.getElementById('modifyStep');
const saveChangesBtn = document.getElementById('saveChangesBtn');
const modifyStatus = document.getElementById('modifyStatus');
const recompileStep = document.getElementById('recompileStep');
const recompileBtn = document.getElementById('recompileBtn');
const recompileProgressContainer = document.getElementById('recompileProgressContainer');
const recompileProgressBar = document.getElementById('recompileProgressBar');
const recompileStatus = document.getElementById('recompileStatus');
const signStep = document.getElementById('signStep');
const signBtn = document.getElementById('signBtn');
const signProgressContainer = document.getElementById('signProgressContainer');
const signProgressBar = document.getElementById('signProgressBar');
const signStatus = document.getElementById('signStatus');
const downloadStep = document.getElementById('downloadStep');
const downloadBtn = document.getElementById('downloadBtn');
const downloadStatus = document.getElementById('downloadStatus');
// Event listeners
fileUpload.addEventListener('click', () => apkFileInput.click());
apkFileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
fileName.textContent = e.target.files[0].name;
} else {
fileName.textContent = 'No file selected';
}
});
// Drag and drop functionality
fileUpload.addEventListener('dragover', (e) => {
e.preventDefault();
fileUpload.style.borderColor = '#4285f4';
fileUpload.style.backgroundColor = '#f8f9fa';
});
fileUpload.addEventListener('dragleave', () => {
fileUpload.style.borderColor = '#ccc';
fileUpload.style.backgroundColor = 'transparent';
});
fileUpload.addEventListener('drop', (e) => {
e.preventDefault();
fileUpload.style.borderColor = '#ccc';
fileUpload.style.backgroundColor = 'transparent';
if (e.dataTransfer.files.length > 0) {
const file = e.dataTransfer.files[0];
if (file.name.endsWith('.apk')) {
apkFileInput.files = e.dataTransfer.files;
fileName.textContent = file.name;
} else {
showStatus(uploadStatus, 'Only APK files are allowed', 'error');
}
}
});
uploadBtn.addEventListener('click', uploadApk);
decompileBtn.addEventListener('click', decompileApk);
saveChangesBtn.addEventListener('click', saveChanges);
recompileBtn.addEventListener('click', recompileApk);
signBtn.addEventListener('click', signApk);
// Functions
function showStatus(element, message, type) {
element.textContent = message;
element.className = 'status ' + type;
}
function resetStatus(element) {
element.textContent = '';
element.className = 'status';
element.style.display = 'none';
}
function updateProgressBar(bar, container, percentage) {
bar.style.width = percentage + '%';
bar.textContent = percentage + '%';
container.style.display = 'block';
}
function hideProgressBar(container) {
container.style.display = 'none';
}
async function uploadApk() {
if (!apkFileInput.files || apkFileInput.files.length === 0) {
showStatus(uploadStatus, 'Please select an APK file first', 'error');
return;
}
const file = apkFileInput.files[0];
const formData = new FormData();
formData.append('file', file);
resetStatus(uploadStatus);
updateProgressBar(uploadProgressBar, uploadProgressContainer, 0);
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
});
const data = await response.json();
if (response.ok) {
currentApk.path = data.filepath;
showStatus(uploadStatus, 'APK uploaded successfully!', 'success');
updateProgressBar(uploadProgressBar, uploadProgressContainer, 100);
// Show next step
decompileStep.classList.remove('hidden');
// Extract and show app icon
extractAppIcon();
} else {
showStatus(uploadStatus, data.error || 'Upload failed', 'error');
hideProgressBar(uploadProgressContainer);
}
} catch (error) {
showStatus(uploadStatus, 'Error uploading file: ' + error.message, 'error');
hideProgressBar(uploadProgressContainer);
}
}
async function extractAppIcon() {
try {
const response = await fetch('/api/extract_icon', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
apk_path: currentApk.path
}),
});
if (response.ok) {
const blob = await response.blob();
const objectURL = URL.createObjectURL(blob);
appIcon.src = objectURL;
appIcon.style.display = 'block';
}
} catch (error) {
console.error('Error extracting app icon:', error);
}
}
async function decompileApk() {
if (!currentApk.path) {
showStatus(decompileStatus, 'No APK file available', 'error');
return;
}
resetStatus(decompileStatus);
updateProgressBar(decompileProgressBar, decompileProgressContainer, 0);
try {
const response = await fetch('/api/decompile', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
apk_path: currentApk.path
}),
});
const data = await response.json();
if (response.ok) {
currentApk.decompiledPath = data.output_dir;
showStatus(decompileStatus, 'APK decompiled successfully!', 'success');
updateProgressBar(decompileProgressBar, decompileProgressContainer, 100);
// Show next step
modifyStep.classList.remove('hidden');
} else {
showStatus(decompileStatus, data.error || 'Decompilation failed', 'error');
hideProgressBar(decompileProgressContainer);
}
} catch (error) {
showStatus(decompileStatus, 'Error decompiling APK: ' + error.message, 'error');
hideProgressBar(decompileProgressContainer);
}
}
async function saveChanges() {
const appName = document.getElementById('appName').value;
const packageName = document.getElementById('packageName').value;
const versionCode = document.getElementById('versionCode').value;
const versionName = document.getElementById('versionName').value;
if (!appName && !packageName && !versionCode && !versionName) {
showStatus(modifyStatus, 'No changes specified', 'error');
return;
}
resetStatus(modifyStatus);
try {
const response = await fetch('/api/modify_manifest', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
decompiled_dir: currentApk.decompiledPath,
modifications: [
{
action: 'set_attribute',
path: '.',
attribute: 'package',
value: packageName || 'com.example.app'
},
{
action: 'set_attribute',
path: '.',
attribute: 'android:versionCode',
value: versionCode || '1'
},
{
action: 'set_attribute',
path: '.',
attribute: 'android:versionName',
value: versionName || '1.0'
},
{
action: 'set_attribute',
path: 'application',
attribute: 'android:label',
value: appName || 'My App'
}
]
}),
});
const data = await response.json();
if (response.ok) {
showStatus(modifyStatus, 'Changes saved successfully!', 'success');
// Show next step
recompileStep.classList.remove('hidden');
} else {
showStatus(modifyStatus, data.error || 'Failed to save changes', 'error');
}
} catch (error) {
showStatus(modifyStatus, 'Error saving changes: ' + error.message, 'error');
}
}
async function recompileApk() {
if (!currentApk.decompiledPath) {
showStatus(recompileStatus, 'No decompiled APK available', 'error');
return;
}
resetStatus(recompileStatus);
updateProgressBar(recompileProgressBar, recompileProgressContainer, 0);
try {
const response = await fetch('/api/recompile', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
decompiled_dir: currentApk.decompiledPath
}),
});
const data = await response.json();
if (response.ok) {
currentApk.modifiedPath = data.output_apk;
showStatus(recompileStatus, 'APK recompiled successfully!', 'success');
updateProgressBar(recompileProgressBar, recompileProgressContainer, 100);
// Show next step
signStep.classList.remove('hidden');
} else {
showStatus(recompileStatus, data.error || 'Recompilation failed', 'error');
hideProgressBar(recompileProgressContainer);
}
} catch (error) {
showStatus(recompileStatus, 'Error recompiling APK: ' + error.message, 'error');
hideProgressBar(recompileProgressContainer);
}
}
async function signApk() {
if (!currentApk.modifiedPath) {
showStatus(signStatus, 'No modified APK available', 'error');
return;
}
resetStatus(signStatus);
updateProgressBar(signProgressBar, signProgressContainer, 0);
try {
const response = await fetch('/api/sign', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
apk_path: currentApk.modifiedPath
}),
});
const data = await response.json();
if (response.ok) {
currentApk.signedPath = data.signed_apk;
showStatus(signStatus, 'APK signed successfully!', 'success');
updateProgressBar(signProgressBar, signProgressContainer, 100);
// Show next step
downloadStep.classList.remove('hidden');
downloadBtn.href = `/api/download?apk_path=${encodeURIComponent(currentApk.signedPath)}`;
downloadBtn.style.display = 'inline-block';
} else {
showStatus(signStatus, data.error || 'Signing failed', 'error');
hideProgressBar(signProgressContainer);
}
} catch (error) {
showStatus(signStatus, 'Error signing APK: ' + error.message, 'error');
hideProgressBar(signProgressContainer);
}
}
</script>
</body>
</html>