Spaces:
Running
Running
Upload 3 files
Browse files- app.py +5 -23
- index.html +9 -18
- manifest.json +24 -0
app.py
CHANGED
|
@@ -392,13 +392,6 @@ def set_coins_fn(u, n):
|
|
| 392 |
db['users'][u]['coins'] = int(n); save_db(db)
|
| 393 |
return f'β
Coin = {n} πͺ'
|
| 394 |
|
| 395 |
-
def ban_fn(u, ban=True):
|
| 396 |
-
db = load_db()
|
| 397 |
-
if u not in db['users']: return 'β User not found'
|
| 398 |
-
db['users'][u]['banned'] = ban
|
| 399 |
-
save_db(db)
|
| 400 |
-
return f"β
{'Banned' if ban else 'Unbanned'}: {u}"
|
| 401 |
-
|
| 402 |
def upd_stat(u, t):
|
| 403 |
db = load_db()
|
| 404 |
if u not in db['users']: return
|
|
@@ -895,6 +888,10 @@ def api_config():
|
|
| 895 |
def google_verify():
|
| 896 |
return 'google-site-verification: googlefd3d91bc095a2620.html', 200, {'Content-Type': 'text/html'}
|
| 897 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 898 |
@app.route('/sitemap.xml')
|
| 899 |
def sitemap():
|
| 900 |
xml = '''<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -1550,8 +1547,6 @@ def api_process_all():
|
|
| 1550 |
u = (request.form.get('username') or '').strip()
|
| 1551 |
if not u: return jsonify(ok=False, msg='β Not logged in')
|
| 1552 |
is_adm = (u == ADMIN_U)
|
| 1553 |
-
if not is_adm and load_db()['users'].get(u, {}).get('banned'):
|
| 1554 |
-
return jsonify(ok=False, msg='β Your account has been banned')
|
| 1555 |
if not is_adm and get_coins(u) < 2:
|
| 1556 |
return jsonify(ok=False, msg='β Not enough coins (need 2)')
|
| 1557 |
# Check free_trial flag β system auto coins only
|
|
@@ -1805,8 +1800,7 @@ def api_users():
|
|
| 1805 |
users = [{'username':k,'coins':v.get('coins',0),
|
| 1806 |
'transcripts':v.get('total_transcripts',0),
|
| 1807 |
'videos':v.get('total_videos',0),
|
| 1808 |
-
'created':v.get('created_at','')[:10]
|
| 1809 |
-
'banned':v.get('banned',False)}
|
| 1810 |
for k,v in db['users'].items()]
|
| 1811 |
return jsonify(ok=True, users=users)
|
| 1812 |
except Exception as e:
|
|
@@ -1826,18 +1820,6 @@ def api_delete_user():
|
|
| 1826 |
except Exception as e:
|
| 1827 |
return jsonify(ok=False, msg=str(e))
|
| 1828 |
|
| 1829 |
-
@app.route('/api/admin/ban_user', methods=['POST'])
|
| 1830 |
-
def api_ban_user():
|
| 1831 |
-
try:
|
| 1832 |
-
d = request.get_json(force=True) or {}
|
| 1833 |
-
if d.get('caller') != ADMIN_U: return jsonify(ok=False, msg='β Admin only')
|
| 1834 |
-
u = d.get('username','').strip()
|
| 1835 |
-
ban = d.get('ban', True)
|
| 1836 |
-
msg = ban_fn(u, ban)
|
| 1837 |
-
return jsonify(ok=True, msg=msg)
|
| 1838 |
-
except Exception as e:
|
| 1839 |
-
return jsonify(ok=False, msg=str(e))
|
| 1840 |
-
|
| 1841 |
@app.route('/api/admin/gen_username')
|
| 1842 |
def api_gen_username():
|
| 1843 |
try:
|
|
|
|
| 392 |
db['users'][u]['coins'] = int(n); save_db(db)
|
| 393 |
return f'β
Coin = {n} πͺ'
|
| 394 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 395 |
def upd_stat(u, t):
|
| 396 |
db = load_db()
|
| 397 |
if u not in db['users']: return
|
|
|
|
| 888 |
def google_verify():
|
| 889 |
return 'google-site-verification: googlefd3d91bc095a2620.html', 200, {'Content-Type': 'text/html'}
|
| 890 |
|
| 891 |
+
@app.route('/manifest.json')
|
| 892 |
+
def pwa_manifest():
|
| 893 |
+
return send_from_directory(str(BASE_DIR), 'manifest.json', mimetype='application/manifest+json')
|
| 894 |
+
|
| 895 |
@app.route('/sitemap.xml')
|
| 896 |
def sitemap():
|
| 897 |
xml = '''<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
| 1547 |
u = (request.form.get('username') or '').strip()
|
| 1548 |
if not u: return jsonify(ok=False, msg='β Not logged in')
|
| 1549 |
is_adm = (u == ADMIN_U)
|
|
|
|
|
|
|
| 1550 |
if not is_adm and get_coins(u) < 2:
|
| 1551 |
return jsonify(ok=False, msg='β Not enough coins (need 2)')
|
| 1552 |
# Check free_trial flag β system auto coins only
|
|
|
|
| 1800 |
users = [{'username':k,'coins':v.get('coins',0),
|
| 1801 |
'transcripts':v.get('total_transcripts',0),
|
| 1802 |
'videos':v.get('total_videos',0),
|
| 1803 |
+
'created':v.get('created_at','')[:10]}
|
|
|
|
| 1804 |
for k,v in db['users'].items()]
|
| 1805 |
return jsonify(ok=True, users=users)
|
| 1806 |
except Exception as e:
|
|
|
|
| 1820 |
except Exception as e:
|
| 1821 |
return jsonify(ok=False, msg=str(e))
|
| 1822 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1823 |
@app.route('/api/admin/gen_username')
|
| 1824 |
def api_gen_username():
|
| 1825 |
try:
|
index.html
CHANGED
|
@@ -2,7 +2,14 @@
|
|
| 2 |
<html lang="en">
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
<title>Recap Studio</title>
|
| 7 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 8 |
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
|
@@ -1517,24 +1524,8 @@ function copyCap(){const t=[OUT_CAP,OUT_TAGS].filter(Boolean).join('\n\n');if(!t
|
|
| 1517 |
async function admCoins(act){const u=document.getElementById('au-'+act).value.trim(),n=parseInt(document.getElementById('an-'+act).value)||0;if(!u){toast('β Enter username');return;}const r=await fetch('/api/admin/coins',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({caller:U,username:u,amount:n,action:act})});const d=await r.json();document.getElementById('admmsg').textContent=d.msg||'';}
|
| 1518 |
async function admCreate(){const u=document.getElementById('au-new').value.trim();const r=await fetch('/api/admin/create_user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({caller:U,username:u,coins:0})});const d=await r.json();document.getElementById('au-res').textContent=d.msg+(d.username?' β '+d.username:'');}
|
| 1519 |
async function admDel(){const u=document.getElementById('au-del').value.trim();if(!u||!confirm('Delete '+u+'?'))return;const r=await fetch('/api/admin/delete_user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({caller:U,username:u})});const d=await r.json();document.getElementById('admmsg').textContent=d.msg;if(d.ok)loadUsers();}
|
| 1520 |
-
async function loadUsers(){
|
| 1521 |
-
const r=await fetch('/api/admin/users?caller='+encodeURIComponent(U));
|
| 1522 |
-
if(!r.ok)return;const d=await r.json();if(!d.ok)return;
|
| 1523 |
-
const w=document.getElementById('utw');
|
| 1524 |
-
if(!d.users.length){w.innerHTML='<div style="font-size:.75rem;color:var(--muted);padding:6px">No users</div>';return;}
|
| 1525 |
-
let h='<table class="ut"><thead><tr><th>User</th><th>πͺ</th><th>Vids</th><th>Status</th><th>Actions</th></tr></thead><tbody>';
|
| 1526 |
-
d.users.forEach(u=>{
|
| 1527 |
-
const banned=u.banned;
|
| 1528 |
-
const rowStyle=banned?'opacity:.5;background:rgba(239,68,68,.05)':'';
|
| 1529 |
-
const banBtn=banned
|
| 1530 |
-
?`<button class="delbtn" style="color:#10b981" onclick="qBan('${u.username}',false)" title="Unban"><i class="fas fa-unlock"></i></button>`
|
| 1531 |
-
:`<button class="delbtn" style="color:#f59e0b" onclick="qBan('${u.username}',true)" title="Ban"><i class="fas fa-ban"></i></button>`;
|
| 1532 |
-
h+=`<tr style="${rowStyle}"><td style="max-width:100px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${u.username}</td><td>${u.coins}</td><td>${u.videos}</td><td>${banned?'<span style="color:#ef4444;font-size:.7rem">π« Banned</span>':'<span style="color:#10b981;font-size:.7rem">β
Active</span>'}</td><td style="display:flex;gap:3px">${banBtn}<button class="delbtn" onclick="qDel('${u.username}')"><i class="fas fa-trash"></i></button></td></tr>`;
|
| 1533 |
-
});
|
| 1534 |
-
w.innerHTML=h+'</tbody></table>';
|
| 1535 |
-
}
|
| 1536 |
async function qDel(u){if(!confirm('Delete '+u+'?'))return;const r=await fetch('/api/admin/delete_user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({caller:U,username:u})});const d=await r.json();toast(d.msg);if(d.ok)loadUsers();}
|
| 1537 |
-
async function qBan(u,ban){const label=ban?'Ban':'Unban';if(!confirm(label+' '+u+'?'))return;const r=await fetch('/api/admin/ban_user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({caller:U,username:u,ban})});const d=await r.json();toast(d.msg);if(d.ok)loadUsers();}
|
| 1538 |
|
| 1539 |
/* ββ PENDING PAYMENTS ββ */
|
| 1540 |
async function loadPendingPayments(){
|
|
|
|
| 2 |
<html lang="en">
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width,initial-scale=1.0,viewport-fit=cover">
|
| 6 |
+
<meta name="mobile-web-app-capable" content="yes">
|
| 7 |
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
| 8 |
+
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
| 9 |
+
<meta name="apple-mobile-web-app-title" content="Recap Studio">
|
| 10 |
+
<meta name="theme-color" content="#1a1a2e">
|
| 11 |
+
<link rel="manifest" href="/manifest.json">
|
| 12 |
+
<link rel="apple-touch-icon" href="https://raw.githubusercontent.com/Shangyi69/psonlineshop/main/logo.png">
|
| 13 |
<title>Recap Studio</title>
|
| 14 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 15 |
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
|
|
|
| 1524 |
async function admCoins(act){const u=document.getElementById('au-'+act).value.trim(),n=parseInt(document.getElementById('an-'+act).value)||0;if(!u){toast('β Enter username');return;}const r=await fetch('/api/admin/coins',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({caller:U,username:u,amount:n,action:act})});const d=await r.json();document.getElementById('admmsg').textContent=d.msg||'';}
|
| 1525 |
async function admCreate(){const u=document.getElementById('au-new').value.trim();const r=await fetch('/api/admin/create_user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({caller:U,username:u,coins:0})});const d=await r.json();document.getElementById('au-res').textContent=d.msg+(d.username?' β '+d.username:'');}
|
| 1526 |
async function admDel(){const u=document.getElementById('au-del').value.trim();if(!u||!confirm('Delete '+u+'?'))return;const r=await fetch('/api/admin/delete_user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({caller:U,username:u})});const d=await r.json();document.getElementById('admmsg').textContent=d.msg;if(d.ok)loadUsers();}
|
| 1527 |
+
async function loadUsers(){const r=await fetch('/api/admin/users?caller='+encodeURIComponent(U));if(!r.ok)return;const d=await r.json();if(!d.ok)return;const w=document.getElementById('utw');if(!d.users.length){w.innerHTML='<div style="font-size:.75rem;color:var(--muted);padding:6px">No users</div>';return;}let h='<table class="ut"><thead><tr><th>Username</th><th>Coins</th><th>Videos</th><th>Created</th><th></th></tr></thead><tbody>';d.users.forEach(u=>{h+=`<tr><td>${u.username}</td><td>πͺ${u.coins}</td><td>${u.videos}</td><td>${u.created||''}</td><td><button class="delbtn" onclick="qDel('${u.username}')"><i class="fas fa-trash"></i></button></td></tr>`;});w.innerHTML=h+'</tbody></table>';}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1528 |
async function qDel(u){if(!confirm('Delete '+u+'?'))return;const r=await fetch('/api/admin/delete_user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({caller:U,username:u})});const d=await r.json();toast(d.msg);if(d.ok)loadUsers();}
|
|
|
|
| 1529 |
|
| 1530 |
/* ββ PENDING PAYMENTS ββ */
|
| 1531 |
async function loadPendingPayments(){
|
manifest.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Recap Studio",
|
| 3 |
+
"short_name": "Recap Studio",
|
| 4 |
+
"description": "AI-powered video recap generator",
|
| 5 |
+
"start_url": "/",
|
| 6 |
+
"display": "standalone",
|
| 7 |
+
"background_color": "#1a1a2e",
|
| 8 |
+
"theme_color": "#1a1a2e",
|
| 9 |
+
"orientation": "portrait",
|
| 10 |
+
"icons": [
|
| 11 |
+
{
|
| 12 |
+
"src": "https://raw.githubusercontent.com/Shangyi69/psonlineshop/main/logo.png",
|
| 13 |
+
"sizes": "192x192",
|
| 14 |
+
"type": "image/png",
|
| 15 |
+
"purpose": "any maskable"
|
| 16 |
+
},
|
| 17 |
+
{
|
| 18 |
+
"src": "https://raw.githubusercontent.com/Shangyi69/psonlineshop/main/logo.png",
|
| 19 |
+
"sizes": "512x512",
|
| 20 |
+
"type": "image/png",
|
| 21 |
+
"purpose": "any maskable"
|
| 22 |
+
}
|
| 23 |
+
]
|
| 24 |
+
}
|