update applovin demo upload
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ApplovinPage(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -19,11 +20,15 @@ func ApplovinPage(w http.ResponseWriter, r *http.Request) {
|
||||
<p class="hint" style="margin-top:0;">Upload HTML files to p.applov.in and get QR codes to scan with the AppLovin Playable Preview app.</p>
|
||||
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
|
||||
<button id="pick" class="secondary">Choose files...</button>
|
||||
<button id="pick" class="secondary">Add files...</button>
|
||||
<button id="clear" class="secondary" style="display:none;">Clear</button>
|
||||
<span id="fileName" class="hint">(no files)</span>
|
||||
</div>
|
||||
<div id="fileList" style="margin-bottom:8px;font-size:12px;opacity:0.85;"></div>
|
||||
<div style="display:flex;gap:8px;">
|
||||
<button id="upload">Upload to AppLovin</button>
|
||||
<button id="saveAll" class="secondary" style="display:none;">Save All QRs...</button>
|
||||
<button id="regenAll" class="secondary" style="display:none;">Regenerate QRs</button>
|
||||
</div>
|
||||
|
||||
<div id="status" style="margin-top:12px;min-height:20px;"></div>
|
||||
@@ -36,21 +41,70 @@ func ApplovinPage(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
<script>
|
||||
const pickBtn = document.getElementById('pick');
|
||||
const clearBtn = document.getElementById('clear');
|
||||
const uploadBtn = document.getElementById('upload');
|
||||
const fileNameEl = document.getElementById('fileName');
|
||||
const fileListEl = document.getElementById('fileList');
|
||||
const statusEl = document.getElementById('status');
|
||||
const resultsEl = document.getElementById('results');
|
||||
const saveAllBtn = document.getElementById('saveAll');
|
||||
const regenAllBtn = document.getElementById('regenAll');
|
||||
let selectedPaths = [];
|
||||
let resultItems = [];
|
||||
|
||||
function basename(p) {
|
||||
const i = Math.max(p.lastIndexOf('/'), p.lastIndexOf('\\'));
|
||||
return i >= 0 ? p.slice(i + 1) : p;
|
||||
}
|
||||
function renderSelection() {
|
||||
if (!selectedPaths.length) {
|
||||
fileNameEl.textContent = '(no files)';
|
||||
fileListEl.innerHTML = '';
|
||||
clearBtn.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
fileNameEl.textContent = selectedPaths.length + ' file' + (selectedPaths.length === 1 ? '' : 's');
|
||||
fileListEl.innerHTML = selectedPaths.map(p => '<div>' + escapeHtml(basename(p)) + '</div>').join('');
|
||||
clearBtn.style.display = '';
|
||||
}
|
||||
|
||||
saveAllBtn.addEventListener('click', async () => {
|
||||
if (!resultItems.length) return;
|
||||
saveAllBtn.disabled = true;
|
||||
try {
|
||||
const r = await fetch('/api/applovin/saveAllQrs', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ items: resultItems }),
|
||||
});
|
||||
const j = await r.json();
|
||||
statusEl.innerHTML = j.ok
|
||||
? '<span class="ok">' + escapeHtml(j.message || 'Saved.') + '</span>'
|
||||
: '<span class="err">' + escapeHtml(j.message || 'Save failed.') + '</span>';
|
||||
} finally {
|
||||
saveAllBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
regenAllBtn.addEventListener('click', () => {
|
||||
resultsEl.querySelectorAll('img[data-qr]').forEach(img => {
|
||||
const base = img.getAttribute('data-qr');
|
||||
img.src = base + '&_=' + Date.now();
|
||||
});
|
||||
});
|
||||
|
||||
clearBtn.addEventListener('click', () => {
|
||||
selectedPaths = [];
|
||||
renderSelection();
|
||||
});
|
||||
|
||||
pickBtn.addEventListener('click', async () => {
|
||||
const r = await fetch('/api/applovin/pick', { method: 'POST' });
|
||||
const j = await r.json();
|
||||
if (j.files && j.files.length) {
|
||||
selectedPaths = j.files.map(f => f.path);
|
||||
const names = j.files.map(f => f.name);
|
||||
fileNameEl.textContent = names.length === 1
|
||||
? names[0]
|
||||
: names.length + ' files: ' + names.slice(0,3).join(', ') + (names.length > 3 ? '...' : '');
|
||||
const added = j.files.map(f => f.path).filter(p => !selectedPaths.includes(p));
|
||||
selectedPaths = selectedPaths.concat(added);
|
||||
renderSelection();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -62,6 +116,9 @@ uploadBtn.addEventListener('click', async () => {
|
||||
return;
|
||||
}
|
||||
uploadBtn.disabled = true;
|
||||
resultItems = [];
|
||||
saveAllBtn.style.display = 'none';
|
||||
regenAllBtn.style.display = 'none';
|
||||
try {
|
||||
const res = await fetch('/api/applovin/upload', {
|
||||
method: 'POST',
|
||||
@@ -113,25 +170,42 @@ function handle(m) {
|
||||
wrap.innerHTML =
|
||||
'<div style="font-weight:600;margin-bottom:8px;">' + escapeHtml(m.name) + '</div>' +
|
||||
'<div style="text-align:center;margin-bottom:8px;">' +
|
||||
'<img src="' + m.qrImg + '" style="background:#fff;padding:8px;border-radius:4px;max-width:100%;" />' +
|
||||
'<img src="' + m.qrImg + '" data-qr="' + m.qrImg + '" style="background:#fff;padding:8px;border-radius:4px;max-width:100%;" />' +
|
||||
'</div>' +
|
||||
'<div style="font-size:11px;opacity:0.8;">Hash: ' + escapeHtml(m.hash) + '</div>';
|
||||
const actions = document.createElement('div');
|
||||
actions.className = 'result-actions';
|
||||
const copyHash = document.createElement('button');
|
||||
copyHash.textContent = 'Copy Hash';
|
||||
copyHash.onclick = () => fetch('/api/clipboard', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({text:m.hash})});
|
||||
const copyUrl = document.createElement('button');
|
||||
copyUrl.textContent = 'Copy URL';
|
||||
copyUrl.className = 'secondary';
|
||||
copyUrl.onclick = () => fetch('/api/clipboard', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({text:m.previewUrl})});
|
||||
const openBtn = document.createElement('button');
|
||||
openBtn.textContent = 'Open';
|
||||
openBtn.className = 'secondary';
|
||||
openBtn.onclick = () => fetch('/api/open', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url:m.previewUrl})});
|
||||
actions.appendChild(copyHash); actions.appendChild(copyUrl); actions.appendChild(openBtn);
|
||||
const saveQr = document.createElement('button');
|
||||
saveQr.textContent = 'Save QR';
|
||||
saveQr.className = 'secondary';
|
||||
saveQr.onclick = async () => {
|
||||
saveQr.disabled = true;
|
||||
try {
|
||||
const r = await fetch('/api/applovin/saveQr', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ name: m.name, url: m.qrImg }),
|
||||
});
|
||||
const j = await r.json();
|
||||
statusEl.innerHTML = j.ok
|
||||
? '<span class="ok">' + escapeHtml(j.message || 'Saved.') + '</span>'
|
||||
: '<span class="err">' + escapeHtml(j.message || 'Save failed.') + '</span>';
|
||||
} finally {
|
||||
saveQr.disabled = false;
|
||||
}
|
||||
};
|
||||
actions.appendChild(openBtn); actions.appendChild(saveQr);
|
||||
wrap.appendChild(actions);
|
||||
resultsEl.appendChild(wrap);
|
||||
resultItems.push({ name: m.name, url: m.qrImg });
|
||||
if (resultItems.length >= 2) {
|
||||
saveAllBtn.style.display = '';
|
||||
regenAllBtn.style.display = '';
|
||||
}
|
||||
} else if (m.type === 'done') {
|
||||
statusEl.innerHTML = '<span class="ok">Done.</span>';
|
||||
}
|
||||
@@ -300,3 +374,114 @@ func ApplovinUpload(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
send(map[string]any{"type": "done"})
|
||||
}
|
||||
|
||||
func qrBaseName(name string) string {
|
||||
base := name
|
||||
if base == "" {
|
||||
base = "qr"
|
||||
}
|
||||
base = regexp.MustCompile(`(?i)\.html?$`).ReplaceAllString(base, "")
|
||||
base = regexp.MustCompile(`[\\/:*?"<>|]`).ReplaceAllString(base, "_")
|
||||
if base == "" {
|
||||
base = "qr"
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
func downloadQR(url, destPath string) error {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("HTTP %d", resp.StatusCode)
|
||||
}
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(destPath, data, 0644)
|
||||
}
|
||||
|
||||
func ApplovinSaveQR(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSON(w, map[string]any{"ok": false, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
if req.URL == "" {
|
||||
writeJSON(w, map[string]any{"ok": false, "message": "Missing URL"})
|
||||
return
|
||||
}
|
||||
base := qrBaseName(req.Name)
|
||||
dest := PickSaveFile("Save QR", base+".png", []FileFilter{{Name: "PNG Image", Extensions: []string{"png"}}}, "")
|
||||
if dest == "" {
|
||||
writeJSON(w, map[string]any{"ok": false, "message": "Cancelled"})
|
||||
return
|
||||
}
|
||||
if err := downloadQR(req.URL, dest); err != nil {
|
||||
writeJSON(w, map[string]any{"ok": false, "message": "Save failed: " + err.Error()})
|
||||
return
|
||||
}
|
||||
writeJSON(w, map[string]any{"ok": true, "message": "Saved QR: " + dest})
|
||||
}
|
||||
|
||||
func ApplovinSaveAllQRs(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Items []struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
} `json:"items"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSON(w, map[string]any{"ok": false, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
if len(req.Items) == 0 {
|
||||
writeJSON(w, map[string]any{"ok": false, "message": "No items"})
|
||||
return
|
||||
}
|
||||
dir := PickFolder("Choose folder to save QRs", "")
|
||||
if dir == "" {
|
||||
writeJSON(w, map[string]any{"ok": false, "message": "Cancelled"})
|
||||
return
|
||||
}
|
||||
saved := 0
|
||||
errs := []string{}
|
||||
for _, it := range req.Items {
|
||||
base := qrBaseName(it.Name)
|
||||
dest := filepath.Join(dir, base+".png")
|
||||
n := 1
|
||||
for fileExists(dest) {
|
||||
dest = filepath.Join(dir, fmt.Sprintf("%s (%d).png", base, n))
|
||||
n++
|
||||
}
|
||||
if err := downloadQR(it.URL, dest); err != nil {
|
||||
errs = append(errs, it.Name+": "+err.Error())
|
||||
continue
|
||||
}
|
||||
saved++
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
writeJSON(w, map[string]any{
|
||||
"ok": false,
|
||||
"message": fmt.Sprintf("Saved %d/%d. Errors: %s", saved, len(req.Items), strings.Join(errs, "; ")),
|
||||
})
|
||||
return
|
||||
}
|
||||
writeJSON(w, map[string]any{
|
||||
"ok": true,
|
||||
"message": fmt.Sprintf("Saved %d QR%s to %s", saved, plural(saved), dir),
|
||||
})
|
||||
}
|
||||
|
||||
func plural(n int) string {
|
||||
if n == 1 {
|
||||
return ""
|
||||
}
|
||||
return "s"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user