Spaces:
Running
Running
Upload 4 files
Browse files
app.py
CHANGED
|
@@ -410,19 +410,46 @@ def get_num_rule(vo_lang='my'):
|
|
| 410 |
else:
|
| 411 |
return NUM_TO_MM_RULE
|
| 412 |
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
'gemini-
|
| 416 |
-
'gemini-
|
| 417 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 418 |
|
| 419 |
-
def call_api(msgs, api='Gemini'):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 420 |
if api == 'DeepSeek':
|
| 421 |
keys, base = DEEPSEEK_KEYS, 'https://api.deepseek.com'
|
| 422 |
models = ['deepseek-chat']
|
| 423 |
else:
|
| 424 |
keys, base = GEMINI_KEYS, 'https://generativelanguage.googleapis.com/v1beta/openai/'
|
| 425 |
-
models =
|
| 426 |
valid = [(i+1, k) for i, k in enumerate(keys) if k]
|
| 427 |
if not valid: raise Exception('No API Key available')
|
| 428 |
if api == 'Gemini':
|
|
@@ -437,7 +464,7 @@ def call_api(msgs, api='Gemini'):
|
|
| 437 |
r = OpenAI(api_key=k, base_url=base, timeout=120.0).chat.completions.create(
|
| 438 |
model=mdl, messages=msgs, max_tokens=8192)
|
| 439 |
if r and r.choices and r.choices[0].message.content:
|
| 440 |
-
print(f'โ
call_api key={n} model={mdl}')
|
| 441 |
return r.choices[0].message.content.strip(), f'โ
Key{n} ({mdl})'
|
| 442 |
except Exception as e:
|
| 443 |
err = str(e); last_err = e
|
|
@@ -445,7 +472,7 @@ def call_api(msgs, api='Gemini'):
|
|
| 445 |
if '401' in err or '403' in err: break
|
| 446 |
if '429' in err: time.sleep(2); break
|
| 447 |
continue
|
| 448 |
-
raise Exception(f'โ All keys/models failed: {last_err}')
|
| 449 |
|
| 450 |
def parse_out(text):
|
| 451 |
sc, ti, ht = '', '', ''
|
|
@@ -999,7 +1026,7 @@ def api_draft():
|
|
| 999 |
sys_p = sys_p + '\n' + get_num_rule(vo_lang)
|
| 1000 |
out_txt, key_n = run_stage('ai', call_api,
|
| 1001 |
[{'role':'system','content':sys_p},
|
| 1002 |
-
{'role':'user','content':f'Language:{lang}\n\n{tr}'}], api=api)
|
| 1003 |
sc, ti, ht = parse_out(out_txt)
|
| 1004 |
|
| 1005 |
rem = -1
|
|
@@ -1515,7 +1542,7 @@ def api_process_all():
|
|
| 1515 |
{'role':'user','content':f'Language:{src_lang}\n\n{tr}'}]
|
| 1516 |
out_txt, _ = run_stage('ai', call_api, tid, _prog,
|
| 1517 |
'โณ AI Script แแแบแธแ
แฎแ
แฑแฌแแทแบแแฑแแแบ', '๐ค AI Script แแฑแธแแฑแแแบโฆ',
|
| 1518 |
-
msgs, api=api_model)
|
| 1519 |
sc, caption_text, hashtags = parse_out(out_txt)
|
| 1520 |
_prog(65, '๐ค AI Script แแผแฎแธแแซแแผแฎ')
|
| 1521 |
|
|
@@ -1740,7 +1767,7 @@ def api_admin_payments():
|
|
| 1740 |
status = request.args.get('status', 'pending')
|
| 1741 |
pdb = load_payments_db()
|
| 1742 |
pays = [p for p in pdb['payments'] if p['status'] == status]
|
| 1743 |
-
clean = [dict(p, slip_image=''
|
| 1744 |
return jsonify(ok=True, payments=clean)
|
| 1745 |
except Exception as e:
|
| 1746 |
return jsonify(ok=False, msg=str(e))
|
|
|
|
| 410 |
else:
|
| 411 |
return NUM_TO_MM_RULE
|
| 412 |
|
| 413 |
+
# โโ Transcript: gemini-3-flash + gemini-2.5-flash (round-robin)
|
| 414 |
+
GEMINI_MODELS_TRANSCRIPT = [
|
| 415 |
+
'gemini-3-flash', # newest
|
| 416 |
+
'gemini-2.5-flash', # stable
|
| 417 |
]
|
| 418 |
+
# โโ Caption: gemini-3.1-flash-lite + gemini-2.5-flash-lite (round-robin)
|
| 419 |
+
GEMINI_MODELS_CAPTION = [
|
| 420 |
+
'gemini-3.1-flash-lite', # newest lite
|
| 421 |
+
'gemini-2.5-flash-lite-preview-06-17', # stable lite
|
| 422 |
+
]
|
| 423 |
+
|
| 424 |
+
_mdl_tr_idx = 0 # transcript model round-robin index
|
| 425 |
+
_mdl_cap_idx = 0 # caption model round-robin index
|
| 426 |
+
_mdl_lock = threading.Lock()
|
| 427 |
+
|
| 428 |
+
def next_model(purpose='transcript'):
|
| 429 |
+
"""Round-robin model selector โ spins like a spinner per call."""
|
| 430 |
+
global _mdl_tr_idx, _mdl_cap_idx
|
| 431 |
+
models = GEMINI_MODELS_TRANSCRIPT if purpose == 'transcript' else GEMINI_MODELS_CAPTION
|
| 432 |
+
with _mdl_lock:
|
| 433 |
+
if purpose == 'transcript':
|
| 434 |
+
idx = _mdl_tr_idx % len(models)
|
| 435 |
+
_mdl_tr_idx += 1
|
| 436 |
+
else:
|
| 437 |
+
idx = _mdl_cap_idx % len(models)
|
| 438 |
+
_mdl_cap_idx += 1
|
| 439 |
+
# Return spinner-ordered list starting from current idx
|
| 440 |
+
return models[idx:] + models[:idx]
|
| 441 |
|
| 442 |
+
def call_api(msgs, api='Gemini', purpose='transcript'):
|
| 443 |
+
"""
|
| 444 |
+
purpose='transcript' โ GEMINI_MODELS_TRANSCRIPT round-robin (gemini-3-flash โ gemini-2.5-flash)
|
| 445 |
+
purpose='caption' โ GEMINI_MODELS_CAPTION round-robin (gemini-3.1-flash-lite โ gemini-2.5-flash-lite)
|
| 446 |
+
"""
|
| 447 |
if api == 'DeepSeek':
|
| 448 |
keys, base = DEEPSEEK_KEYS, 'https://api.deepseek.com'
|
| 449 |
models = ['deepseek-chat']
|
| 450 |
else:
|
| 451 |
keys, base = GEMINI_KEYS, 'https://generativelanguage.googleapis.com/v1beta/openai/'
|
| 452 |
+
models = next_model(purpose) # spinner: returns rotation-ordered list
|
| 453 |
valid = [(i+1, k) for i, k in enumerate(keys) if k]
|
| 454 |
if not valid: raise Exception('No API Key available')
|
| 455 |
if api == 'Gemini':
|
|
|
|
| 464 |
r = OpenAI(api_key=k, base_url=base, timeout=120.0).chat.completions.create(
|
| 465 |
model=mdl, messages=msgs, max_tokens=8192)
|
| 466 |
if r and r.choices and r.choices[0].message.content:
|
| 467 |
+
print(f'โ
call_api key={n} model={mdl} purpose={purpose}')
|
| 468 |
return r.choices[0].message.content.strip(), f'โ
Key{n} ({mdl})'
|
| 469 |
except Exception as e:
|
| 470 |
err = str(e); last_err = e
|
|
|
|
| 472 |
if '401' in err or '403' in err: break
|
| 473 |
if '429' in err: time.sleep(2); break
|
| 474 |
continue
|
| 475 |
+
raise Exception(f'โ All keys/models failed ({purpose}): {last_err}')
|
| 476 |
|
| 477 |
def parse_out(text):
|
| 478 |
sc, ti, ht = '', '', ''
|
|
|
|
| 1026 |
sys_p = sys_p + '\n' + get_num_rule(vo_lang)
|
| 1027 |
out_txt, key_n = run_stage('ai', call_api,
|
| 1028 |
[{'role':'system','content':sys_p},
|
| 1029 |
+
{'role':'user','content':f'Language:{lang}\n\n{tr}'}], api=api, purpose='transcript')
|
| 1030 |
sc, ti, ht = parse_out(out_txt)
|
| 1031 |
|
| 1032 |
rem = -1
|
|
|
|
| 1542 |
{'role':'user','content':f'Language:{src_lang}\n\n{tr}'}]
|
| 1543 |
out_txt, _ = run_stage('ai', call_api, tid, _prog,
|
| 1544 |
'โณ AI Script แแแบแธแ
แฎแ
แฑแฌแแทแบแแฑแแแบ', '๐ค AI Script แแฑแธแแฑแแแบโฆ',
|
| 1545 |
+
msgs, api=api_model, purpose='transcript')
|
| 1546 |
sc, caption_text, hashtags = parse_out(out_txt)
|
| 1547 |
_prog(65, '๐ค AI Script แแผแฎแธแแซแแผแฎ')
|
| 1548 |
|
|
|
|
| 1767 |
status = request.args.get('status', 'pending')
|
| 1768 |
pdb = load_payments_db()
|
| 1769 |
pays = [p for p in pdb['payments'] if p['status'] == status]
|
| 1770 |
+
clean = [dict(p, slip_image='') for p in pays]
|
| 1771 |
return jsonify(ok=True, payments=clean)
|
| 1772 |
except Exception as e:
|
| 1773 |
return jsonify(ok=False, msg=str(e))
|
bot.py
CHANGED
|
@@ -619,34 +619,48 @@ async def on_callback(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
| 619 |
pay['updated_at'] = _dt.now().isoformat()
|
| 620 |
save_payments_db(pdb)
|
| 621 |
add_coins_fn(username, coins_add, 'admin_bot')
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
| 625 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 626 |
db = load_db()
|
| 627 |
tg_id = db['users'].get(username, {}).get('tg_chat_id')
|
|
|
|
| 628 |
if tg_id:
|
| 629 |
try:
|
| 630 |
await ctx.bot.send_message(tg_id,
|
| 631 |
-
f"
|
|
|
|
|
|
|
|
|
|
| 632 |
parse_mode=ParseMode.MARKDOWN)
|
| 633 |
except: pass
|
| 634 |
else: # reject
|
| 635 |
pay['status'] = 'rejected'
|
| 636 |
pay['updated_at'] = _dt.now().isoformat()
|
| 637 |
save_payments_db(pdb)
|
| 638 |
-
new_cap = (q.message.caption or '') + "\n\nโ Rejected"
|
| 639 |
-
try:
|
| 640 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 641 |
db = load_db()
|
| 642 |
tg_id = db['users'].get(username, {}).get('tg_chat_id')
|
| 643 |
if tg_id:
|
| 644 |
try:
|
| 645 |
await ctx.bot.send_message(tg_id,
|
| 646 |
-
f"โ *Payment แแผแแบแธแแแบแแผแแบแธแแถแแแแบ*\n
|
|
|
|
|
|
|
| 647 |
parse_mode=ParseMode.MARKDOWN)
|
| 648 |
except: pass
|
| 649 |
-
await q.answer()
|
| 650 |
return
|
| 651 |
|
| 652 |
if data.startswith('cancel|'):
|
|
|
|
| 619 |
pay['updated_at'] = _dt.now().isoformat()
|
| 620 |
save_payments_db(pdb)
|
| 621 |
add_coins_fn(username, coins_add, 'admin_bot')
|
| 622 |
+
# Edit original message to show approved status + remove buttons
|
| 623 |
+
new_cap = (q.message.caption or q.message.text or '') + f"\n\nโ
<b>Approved!</b> +{coins_add} coins โ <code>{username}</code>"
|
| 624 |
+
try:
|
| 625 |
+
await q.edit_message_caption(caption=new_cap, parse_mode=ParseMode.HTML, reply_markup=None)
|
| 626 |
+
except:
|
| 627 |
+
try: await q.edit_message_text(text=new_cap, parse_mode=ParseMode.HTML, reply_markup=None)
|
| 628 |
+
except: pass
|
| 629 |
+
await q.answer(f"โ
Approved +{coins_add} coins โ {username}", show_alert=False)
|
| 630 |
+
# Notify user with new balance
|
| 631 |
db = load_db()
|
| 632 |
tg_id = db['users'].get(username, {}).get('tg_chat_id')
|
| 633 |
+
new_bal = db['users'].get(username, {}).get('coins', 0)
|
| 634 |
if tg_id:
|
| 635 |
try:
|
| 636 |
await ctx.bot.send_message(tg_id,
|
| 637 |
+
f"๐ *Coins แแแทแบแแผแฎแธแแซแแผแฎ!*\n"
|
| 638 |
+
f"๐ช *+{coins_add} Coins* แแฑแฌแแบแแผแฎ\n"
|
| 639 |
+
f"๐ฐ แแแบแแปแแบ โ *{new_bal} Coins*\n"
|
| 640 |
+
f"๐ `{payment_id}`",
|
| 641 |
parse_mode=ParseMode.MARKDOWN)
|
| 642 |
except: pass
|
| 643 |
else: # reject
|
| 644 |
pay['status'] = 'rejected'
|
| 645 |
pay['updated_at'] = _dt.now().isoformat()
|
| 646 |
save_payments_db(pdb)
|
| 647 |
+
new_cap = (q.message.caption or q.message.text or '') + "\n\nโ <b>Rejected</b>"
|
| 648 |
+
try:
|
| 649 |
+
await q.edit_message_caption(caption=new_cap, parse_mode=ParseMode.HTML, reply_markup=None)
|
| 650 |
+
except:
|
| 651 |
+
try: await q.edit_message_text(text=new_cap, parse_mode=ParseMode.HTML, reply_markup=None)
|
| 652 |
+
except: pass
|
| 653 |
+
await q.answer("โ Rejected", show_alert=False)
|
| 654 |
db = load_db()
|
| 655 |
tg_id = db['users'].get(username, {}).get('tg_chat_id')
|
| 656 |
if tg_id:
|
| 657 |
try:
|
| 658 |
await ctx.bot.send_message(tg_id,
|
| 659 |
+
f"โ *Payment แแผแแบแธแแแบแแผแแบแธแแถแแแแบ*\n"
|
| 660 |
+
f"๐ `{payment_id}`\n"
|
| 661 |
+
f"แแฑแธแแผแแบแธแแแบ โ @{ADMIN_TG_USERNAME}",
|
| 662 |
parse_mode=ParseMode.MARKDOWN)
|
| 663 |
except: pass
|
|
|
|
| 664 |
return
|
| 665 |
|
| 666 |
if data.startswith('cancel|'):
|
index.html
CHANGED
|
@@ -298,72 +298,189 @@ body{background:var(--bg);color:var(--text);font-family:var(--F);min-height:100v
|
|
| 298 |
.modal-close:hover{border-color:var(--red);color:var(--red)}
|
| 299 |
|
| 300 |
/* โโ PAYMENT MODAL โโ */
|
| 301 |
-
.pov{display:none;position:fixed;inset:0;z-index:400;
|
|
|
|
|
|
|
| 302 |
.pov.on{display:flex}
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
@keyframes
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
.
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
.
|
| 318 |
-
.
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 329 |
.pkbz-row:last-child{margin-bottom:0}
|
| 330 |
-
.pkbz-lbl{font-size:.
|
| 331 |
-
.pkbz-val{font-size:.
|
| 332 |
-
.pcopy{
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
.
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
.pslip-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
.pslip-drop input{position:absolute;inset:0;opacity:0;cursor:pointer;width:100%;height:100%}
|
| 341 |
-
.pslip-drop .pi{font-size:
|
| 342 |
-
|
| 343 |
-
.pslip-
|
| 344 |
-
.
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 348 |
.psubmit.on{display:block}
|
| 349 |
-
.psubmit:hover:not(:disabled){
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 354 |
.ph-top{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:8px}
|
| 355 |
-
.ph-coins{font-size:1.
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
.ph-badge
|
| 359 |
-
.ph-badge.
|
| 360 |
-
.ph-
|
| 361 |
-
.ph-
|
| 362 |
-
.
|
| 363 |
-
.
|
| 364 |
-
|
| 365 |
-
border-radius:9px;cursor:pointer;transition:.2s}
|
| 366 |
-
.tab2 button.on{border-color:var(--primary);background:linear-gradient(135deg,#ede9fe,#ddd6fe);color:var(--primary)}
|
| 367 |
</style>
|
| 368 |
</head>
|
| 369 |
<body>
|
|
@@ -600,33 +717,8 @@ body{background:var(--bg);color:var(--text);font-family:var(--F);min-height:100v
|
|
| 600 |
<div id="admmsg" class="admmsg"></div>
|
| 601 |
<button style="padding:8px 14px;background:var(--bg);border:1.5px solid var(--border);border-radius:8px;color:var(--muted);font-family:var(--F);font-size:.75rem;font-weight:600;cursor:pointer;margin-top:4px;transition:.2s" onmouseover="this.style.borderColor='var(--primary)'" onmouseout="this.style.borderColor='var(--border)'" onclick="loadUsers()"><i class="fas fa-users"></i> Load Users</button>
|
| 602 |
<div id="utw" style="overflow-x:auto"></div>
|
| 603 |
-
|
| 604 |
-
<!-- โโ PENDING PAYMENTS โโ -->
|
| 605 |
-
<div style="margin-top:18px;border-top:1.5px solid var(--border);padding-top:14px">
|
| 606 |
-
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">
|
| 607 |
-
<div style="font-weight:700;font-size:.85rem;color:var(--text);display:flex;align-items:center;gap:7px">
|
| 608 |
-
<span style="background:#fff7ed;color:#92400e;border:1px solid #fcd34d;border-radius:6px;padding:3px 8px;font-size:.72rem;font-weight:700">โณ Pending</span>
|
| 609 |
-
Payments
|
| 610 |
-
<span id="adm-pay-count" style="background:var(--red);color:#fff;border-radius:20px;padding:1px 8px;font-size:.7rem;display:none">0</span>
|
| 611 |
-
</div>
|
| 612 |
-
<button onclick="loadAdminPays()" style="padding:5px 11px;background:var(--primary);color:#fff;border:none;border-radius:7px;font-family:var(--F);font-size:.73rem;font-weight:600;cursor:pointer;transition:.2s">๐ Refresh</button>
|
| 613 |
-
</div>
|
| 614 |
-
<div id="adm-pay-list" style="display:flex;flex-direction:column;gap:9px">
|
| 615 |
-
<div style="text-align:center;font-size:.78rem;color:var(--muted);padding:14px">
|
| 616 |
-
Refresh แแพแญแแบแแผแฎแธ Pending Payments แแผแแทแบแแซ
|
| 617 |
-
</div>
|
| 618 |
-
</div>
|
| 619 |
-
</div>
|
| 620 |
</div>
|
| 621 |
|
| 622 |
-
<!-- SLIP LIGHTBOX -->
|
| 623 |
-
<div id="slip-lb" onclick="closeSlipLb()" style="display:none;position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,.85);backdrop-filter:blur(6px);align-items:center;justify-content:center;flex-direction:column;gap:12px">
|
| 624 |
-
<button onclick="closeSlipLb()" style="position:absolute;top:18px;right:18px;background:rgba(255,255,255,.15);border:none;border-radius:50%;width:38px;height:38px;color:#fff;font-size:1.1rem;cursor:pointer">โ</button>
|
| 625 |
-
<img id="slip-lb-img" src="" style="max-width:90vw;max-height:78vh;border-radius:12px;box-shadow:0 8px 40px rgba(0,0,0,.5);object-fit:contain">
|
| 626 |
-
<div id="slip-lb-info" style="color:rgba(255,255,255,.8);font-size:.82rem;text-align:center"></div>
|
| 627 |
-
<div style="display:flex;gap:10px" id="slip-lb-btns"></div>
|
| 628 |
-
</div>
|
| 629 |
-
|
| 630 |
</div>
|
| 631 |
</div>
|
| 632 |
|
|
@@ -761,7 +853,7 @@ function eApp(u,coins,isAdm,displayName){
|
|
| 761 |
document.getElementById('dav').textContent=dn[0].toUpperCase();
|
| 762 |
document.getElementById('dcoins').textContent=isAdm?'โ':coins;
|
| 763 |
fetch('/api/config').then(r=>r.json()).then(d=>{if(d.admin_tg)window._ADMIN_TG=d.admin_tg;}).catch(()=>{});
|
| 764 |
-
if(isAdm){document.getElementById('admp').style.display='block';document.getElementById('adm-dlink').style.display='flex';document.getElementById('cpill').style.display='none';document.getElementById('dcoins').textContent='โ';
|
| 765 |
renderV();
|
| 766 |
window.addEventListener('resize',()=>{const v=document.getElementById('pvid');if(v.src&&v.videoWidth)syncCanvasInner(v);});
|
| 767 |
fetch('/api/gemini_voices').then(r=>r.ok?r.json():null).then(d=>{if(d?.ok){GV=d.voices;if(document.getElementById('engine').value==='gemini')renderV();}}).catch(()=>{});
|
|
@@ -805,8 +897,12 @@ async function initPayModal(){
|
|
| 805 |
}
|
| 806 |
|
| 807 |
function renderPkgs(){
|
|
|
|
|
|
|
| 808 |
document.getElementById('ppkgs').innerHTML=_pkgs.map((p,i)=>`
|
| 809 |
<div class="ppkg${i===1?' pop':''}" id="ppkg${i}" onclick="selPkg(${i})">
|
|
|
|
|
|
|
| 810 |
<div class="ppkg-c">${p.coins}</div>
|
| 811 |
<div class="ppkg-u">Coins</div>
|
| 812 |
<div class="ppkg-p">${p.price}</div>
|
|
@@ -818,6 +914,8 @@ function selPkg(i){
|
|
| 818 |
document.getElementById('ppkg'+i).classList.add('sel');
|
| 819 |
_selPkg=_pkgs[i];
|
| 820 |
document.getElementById('pinfo').textContent=`โ
${_selPkg.coins} Coins โ ${_selPkg.price} แแฝแฑแธแแปแแบแแฌแธแแแบ`;
|
|
|
|
|
|
|
| 821 |
checkPReady();
|
| 822 |
}
|
| 823 |
|
|
@@ -882,7 +980,7 @@ async function loadPayHistory(){
|
|
| 882 |
<span class="ph-badge ${p.status}">${lbl[p.status]||p.status}</span>
|
| 883 |
</div>
|
| 884 |
<div class="ph-meta">๐ ${p.id} ยท ${p.created_at.slice(0,16).replace('T',' ')}</div>
|
| 885 |
-
${p.admin_note?`<div style="margin-top:
|
| 886 |
</div>`).join('');
|
| 887 |
}catch(e){ el.innerHTML=`<div class="ph-empty">โ ${e.message}</div>`; }
|
| 888 |
}
|
|
@@ -1176,94 +1274,6 @@ async function admDel(){const u=document.getElementById('au-del').value.trim();i
|
|
| 1176 |
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>';}
|
| 1177 |
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();}
|
| 1178 |
|
| 1179 |
-
/* โโ ADMIN PENDING PAYMENTS โโ */
|
| 1180 |
-
async function loadAdminPays(){
|
| 1181 |
-
const el=document.getElementById('adm-pay-list');
|
| 1182 |
-
el.innerHTML='<div style="text-align:center;font-size:.78rem;color:var(--muted);padding:14px">โณ Loadingโฆ</div>';
|
| 1183 |
-
try{
|
| 1184 |
-
const r=await fetch('/api/admin/payments?caller='+encodeURIComponent(U)+'&status=pending');
|
| 1185 |
-
const d=await r.json();
|
| 1186 |
-
if(!d.ok){el.innerHTML='<div style="text-align:center;font-size:.78rem;color:var(--red);padding:14px">โ '+(d.msg||'Error')+'</div>';return;}
|
| 1187 |
-
const pays=d.payments||[];
|
| 1188 |
-
const cnt=document.getElementById('adm-pay-count');
|
| 1189 |
-
if(pays.length){cnt.textContent=pays.length;cnt.style.display='inline';}
|
| 1190 |
-
else{cnt.style.display='none';}
|
| 1191 |
-
if(!pays.length){el.innerHTML='<div style="text-align:center;font-size:.78rem;color:var(--muted);padding:14px">โ
Pending payment แแแพแญแแซ</div>';return;}
|
| 1192 |
-
el.innerHTML=pays.map(p=>`
|
| 1193 |
-
<div id="apc-${p.id}" style="background:var(--bg);border:1.5px solid #fcd34d;border-radius:12px;padding:12px;position:relative">
|
| 1194 |
-
<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:8px;flex-wrap:wrap">
|
| 1195 |
-
<div>
|
| 1196 |
-
<div style="font-weight:700;font-size:.85rem;color:var(--text)">๐ค ${p.username}</div>
|
| 1197 |
-
<div style="font-size:.75rem;color:var(--muted);margin-top:2px">๐ช ${p.coins} Coins ยท ${p.price} ยท ๐ <code style="font-size:.7rem">${p.id}</code></div>
|
| 1198 |
-
<div style="font-size:.7rem;color:var(--muted2);margin-top:2px">${(p.created_at||'').slice(0,16).replace('T',' ')}</div>
|
| 1199 |
-
</div>
|
| 1200 |
-
<span style="background:#fff7ed;color:#92400e;border:1px solid #fcd34d;border-radius:6px;padding:2px 8px;font-size:.7rem;font-weight:700;white-space:nowrap">โณ Pending</span>
|
| 1201 |
-
</div>
|
| 1202 |
-
<div style="display:flex;gap:7px;margin-top:10px;flex-wrap:wrap">
|
| 1203 |
-
${p.has_slip!==false?`<button onclick="viewSlip('${p.id}','${p.username}',${p.coins},'${p.price}')" style="padding:6px 12px;background:var(--bg2);border:1.5px solid var(--border2);border-radius:8px;font-family:var(--F);font-size:.75rem;font-weight:600;cursor:pointer;color:var(--text);transition:.2s" onmouseover="this.style.borderColor='var(--primary)'" onmouseout="this.style.borderColor='var(--border2)'">๐ผ Slip แแผแแทแบ</button>`:'<span style="font-size:.72rem;color:var(--muted);padding:6px 0">๐ Slip แแแซ</span>'}
|
| 1204 |
-
<button onclick="admApprove('${p.id}','${p.username}',${p.coins})" style="padding:6px 14px;background:var(--green);color:#fff;border:none;border-radius:8px;font-family:var(--F);font-size:.75rem;font-weight:600;cursor:pointer;transition:.2s">โ
Approve +${p.coins}</button>
|
| 1205 |
-
<button onclick="admReject('${p.id}','${p.username}')" style="padding:6px 14px;background:var(--red);color:#fff;border:none;border-radius:8px;font-family:var(--F);font-size:.75rem;font-weight:600;cursor:pointer;transition:.2s">โ Reject</button>
|
| 1206 |
-
</div>
|
| 1207 |
-
</div>`).join('');
|
| 1208 |
-
}catch(e){el.innerHTML=`<div style="text-align:center;font-size:.78rem;color:var(--red);padding:14px">โ ${e.message}</div>`;}
|
| 1209 |
-
}
|
| 1210 |
-
|
| 1211 |
-
async function admApprove(pid,uname,coins){
|
| 1212 |
-
if(!confirm(`โ
${uname} แแญแฏ +${coins} Coins Approve แแแฏแแบแแฑแฌแแบแแแบ แแฑแแปแฌแแซแแแฌแธ?`)) return;
|
| 1213 |
-
try{
|
| 1214 |
-
const r=await fetch('/api/admin/payment/approve',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({caller:U,payment_id:pid})});
|
| 1215 |
-
const d=await r.json();
|
| 1216 |
-
if(d.ok){
|
| 1217 |
-
toast('โ
'+d.msg);
|
| 1218 |
-
const card=document.getElementById('apc-'+pid);
|
| 1219 |
-
if(card){card.style.opacity='0.4';card.style.pointerEvents='none';setTimeout(()=>{card.remove();},1500);}
|
| 1220 |
-
const cnt=document.getElementById('adm-pay-count');
|
| 1221 |
-
const cur=parseInt(cnt.textContent)||0;
|
| 1222 |
-
if(cur-1<=0){cnt.style.display='none';}else{cnt.textContent=cur-1;}
|
| 1223 |
-
} else { toast('โ '+(d.msg||'Failed')); }
|
| 1224 |
-
}catch(e){toast('โ '+e.message);}
|
| 1225 |
-
}
|
| 1226 |
-
|
| 1227 |
-
async function admReject(pid,uname){
|
| 1228 |
-
const note=prompt(`โ ${uname} แ Payment Reject แแแบ โ แกแแผแฑแฌแแบแธแแผแแปแแบ (Optional):`)||'';
|
| 1229 |
-
try{
|
| 1230 |
-
const r=await fetch('/api/admin/payment/reject',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({caller:U,payment_id:pid,note})});
|
| 1231 |
-
const d=await r.json();
|
| 1232 |
-
if(d.ok){
|
| 1233 |
-
toast('โ Rejected โ '+uname);
|
| 1234 |
-
const card=document.getElementById('apc-'+pid);
|
| 1235 |
-
if(card){card.style.opacity='0.4';card.style.pointerEvents='none';setTimeout(()=>{card.remove();},1500);}
|
| 1236 |
-
} else { toast('โ '+(d.msg||'Failed')); }
|
| 1237 |
-
}catch(e){toast('โ '+e.message);}
|
| 1238 |
-
}
|
| 1239 |
-
|
| 1240 |
-
async function viewSlip(pid,uname,coins,price){
|
| 1241 |
-
const lb=document.getElementById('slip-lb');
|
| 1242 |
-
lb.style.display='flex';
|
| 1243 |
-
document.getElementById('slip-lb-img').src='';
|
| 1244 |
-
document.getElementById('slip-lb-info').textContent='โณ Loading slipโฆ';
|
| 1245 |
-
document.getElementById('slip-lb-btns').innerHTML='';
|
| 1246 |
-
try{
|
| 1247 |
-
const r=await fetch(`/api/admin/payment/slip/${pid}?caller=${encodeURIComponent(U)}`);
|
| 1248 |
-
const d=await r.json();
|
| 1249 |
-
if(d.ok&&d.slip_image){
|
| 1250 |
-
document.getElementById('slip-lb-img').src=d.slip_image;
|
| 1251 |
-
document.getElementById('slip-lb-info').textContent=`๐ค ${uname} ยท ๐ช ${coins} Coins ยท ${price} ยท ๐ ${pid}`;
|
| 1252 |
-
document.getElementById('slip-lb-btns').innerHTML=`
|
| 1253 |
-
<button onclick="closeSlipLb();admApprove('${pid}','${uname}',${coins})" style="padding:9px 20px;background:var(--green);color:#fff;border:none;border-radius:9px;font-family:var(--F);font-size:.8rem;font-weight:700;cursor:pointer">โ
Approve +${coins}</button>
|
| 1254 |
-
<button onclick="closeSlipLb();admReject('${pid}','${uname}')" style="padding:9px 20px;background:var(--red);color:#fff;border:none;border-radius:9px;font-family:var(--F);font-size:.8rem;font-weight:700;cursor:pointer">โ Reject</button>`;
|
| 1255 |
-
} else {
|
| 1256 |
-
document.getElementById('slip-lb-info').textContent='๐ Slip แแฏแถ แแแฝแฑแทแแซ';
|
| 1257 |
-
}
|
| 1258 |
-
}catch(e){document.getElementById('slip-lb-info').textContent='โ '+e.message;}
|
| 1259 |
-
}
|
| 1260 |
-
|
| 1261 |
-
function closeSlipLb(){
|
| 1262 |
-
const lb=document.getElementById('slip-lb');
|
| 1263 |
-
lb.style.display='none';
|
| 1264 |
-
document.getElementById('slip-lb-img').src='';
|
| 1265 |
-
}
|
| 1266 |
-
|
| 1267 |
/* โโ UTILS โโ */
|
| 1268 |
function toast(m){const e=document.getElementById('toast');e.textContent=m;e.classList.add('show');setTimeout(()=>e.classList.remove('show'),2800);}
|
| 1269 |
|
|
@@ -1289,10 +1299,14 @@ document.addEventListener('keydown',e=>{if(e.key==='Enter'&&document.getElementB
|
|
| 1289 |
<!-- โโ PAYMENT MODAL โโ -->
|
| 1290 |
<div class="pov" id="pov" onclick="if(event.target===this)closePay()">
|
| 1291 |
<div class="psheet">
|
|
|
|
|
|
|
| 1292 |
<div class="psheet-hdr">
|
| 1293 |
-
<h2
|
| 1294 |
<button class="pclose" onclick="closePay()"><i class="fas fa-times"></i></button>
|
| 1295 |
</div>
|
|
|
|
|
|
|
| 1296 |
<div class="tab2">
|
| 1297 |
<button id="ptab-buy" class="on" onclick="showPayTab('buy')">๐ แแแบแแแบ</button>
|
| 1298 |
<button id="ptab-his" onclick="showPayTab('his')">๐ แแพแแบแแแบแธ</button>
|
|
@@ -1300,56 +1314,74 @@ document.addEventListener('keydown',e=>{if(e.key==='Enter'&&document.getElementB
|
|
| 1300 |
|
| 1301 |
<!-- BUY TAB -->
|
| 1302 |
<div id="ptab-buy-content">
|
|
|
|
|
|
|
| 1303 |
<div class="ppkgs" id="ppkgs"></div>
|
|
|
|
|
|
|
| 1304 |
<div class="pinfo" id="pinfo">Package แแ
แบแแฏ แแฝแฑแธแแปแแบแแซ</div>
|
|
|
|
|
|
|
| 1305 |
<div class="pkbz">
|
| 1306 |
<div class="pkbz-t">๐ฑ KBZ Pay / Wave / AYA Pay</div>
|
| 1307 |
<div class="pkbz-row">
|
| 1308 |
<span class="pkbz-lbl">Name</span>
|
| 1309 |
-
<div style="display:flex;align-items:center;gap:
|
| 1310 |
<span class="pkbz-val" id="p-kbz-name">โ</span>
|
| 1311 |
-
<button class="pcopy" onclick="pcopy('p-kbz-name')">Copy</button>
|
| 1312 |
</div>
|
| 1313 |
</div>
|
| 1314 |
<div class="pkbz-row">
|
| 1315 |
<span class="pkbz-lbl">Phone</span>
|
| 1316 |
-
<div style="display:flex;align-items:center;gap:
|
| 1317 |
<span class="pkbz-val" id="p-kbz-num">โ</span>
|
| 1318 |
-
<button class="pcopy" onclick="pcopy('p-kbz-num')">Copy</button>
|
| 1319 |
</div>
|
| 1320 |
</div>
|
| 1321 |
</div>
|
| 1322 |
-
|
|
|
|
|
|
|
| 1323 |
<div class="pkbz-t">๐ฆ SCB Bank</div>
|
| 1324 |
<div class="pkbz-row">
|
| 1325 |
<span class="pkbz-lbl">Name</span>
|
| 1326 |
-
<div style="display:flex;align-items:center;gap:
|
| 1327 |
<span class="pkbz-val" id="p-scb-name">โ</span>
|
| 1328 |
-
<button class="pcopy" onclick="pcopy('p-scb-name')">Copy</button>
|
| 1329 |
</div>
|
| 1330 |
</div>
|
| 1331 |
<div class="pkbz-row">
|
| 1332 |
<span class="pkbz-lbl">Account</span>
|
| 1333 |
-
<div style="display:flex;align-items:center;gap:
|
| 1334 |
<span class="pkbz-val" id="p-scb-num">โ</span>
|
| 1335 |
-
<button class="pcopy" onclick="pcopy('p-scb-num')">Copy</button>
|
| 1336 |
</div>
|
| 1337 |
</div>
|
| 1338 |
</div>
|
|
|
|
|
|
|
| 1339 |
<span class="pslip-label">๐ธ แแฝแฑแแฝแพแฒ Slip แแฏแถ แแแบแแฑแธแแซ</span>
|
| 1340 |
<div class="pslip-drop" id="pslip-drop">
|
| 1341 |
<input type="file" accept="image/*" id="pslip-input" onchange="handlePSlip(this)">
|
| 1342 |
<div class="pi" id="pslip-icon">๐</div>
|
| 1343 |
-
<div class="pt" id="pslip-txt">Slip แแฏแถ
|
| 1344 |
<img id="pslip-prev" class="pslip-preview">
|
| 1345 |
</div>
|
| 1346 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1347 |
</div>
|
| 1348 |
|
| 1349 |
<!-- HISTORY TAB -->
|
| 1350 |
<div id="ptab-his-content" style="display:none">
|
| 1351 |
-
<div
|
|
|
|
|
|
|
| 1352 |
</div>
|
|
|
|
| 1353 |
</div>
|
| 1354 |
</div>
|
| 1355 |
|
|
|
|
| 298 |
.modal-close:hover{border-color:var(--red);color:var(--red)}
|
| 299 |
|
| 300 |
/* โโ PAYMENT MODAL โโ */
|
| 301 |
+
.pov{display:none;position:fixed;inset:0;z-index:400;
|
| 302 |
+
background:rgba(8,8,20,.75);backdrop-filter:blur(12px);
|
| 303 |
+
align-items:flex-end;justify-content:center}
|
| 304 |
.pov.on{display:flex}
|
| 305 |
+
|
| 306 |
+
@keyframes sheetUp{from{transform:translateY(100%);opacity:0}to{transform:translateY(0);opacity:1}}
|
| 307 |
+
@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
|
| 308 |
+
@keyframes shimmer{0%{background-position:200% center}100%{background-position:-200% center}}
|
| 309 |
+
@keyframes pulse-glow{0%,100%{box-shadow:0 0 0 0 rgba(139,92,246,.3)}50%{box-shadow:0 0 0 8px rgba(139,92,246,0)}}
|
| 310 |
+
|
| 311 |
+
.psheet{
|
| 312 |
+
position:relative;width:100%;max-width:520px;
|
| 313 |
+
background:linear-gradient(165deg,#0f0f1e 0%,#13112b 60%,#0e1628 100%);
|
| 314 |
+
border-radius:28px 28px 0 0;
|
| 315 |
+
padding:0 0 44px;max-height:93vh;overflow-y:auto;
|
| 316 |
+
box-shadow:0 -4px 60px rgba(91,76,245,.25),0 -1px 0 rgba(255,255,255,.06);
|
| 317 |
+
animation:sheetUp .32s cubic-bezier(.34,1.1,.64,1)}
|
| 318 |
+
.psheet::-webkit-scrollbar{width:3px}
|
| 319 |
+
.psheet::-webkit-scrollbar-track{background:transparent}
|
| 320 |
+
.psheet::-webkit-scrollbar-thumb{background:rgba(139,92,246,.3);border-radius:10px}
|
| 321 |
+
|
| 322 |
+
/* header */
|
| 323 |
+
.psheet-hdr{
|
| 324 |
+
padding:20px 22px 0;
|
| 325 |
+
display:flex;align-items:center;justify-content:space-between;
|
| 326 |
+
position:sticky;top:0;z-index:2;
|
| 327 |
+
background:linear-gradient(165deg,#0f0f1e,#13112b);
|
| 328 |
+
padding-bottom:16px;
|
| 329 |
+
border-bottom:1px solid rgba(255,255,255,.06)}
|
| 330 |
+
.psheet-hdr h2{font-size:1.05rem;font-weight:800;color:#fff;display:flex;align-items:center;gap:8px}
|
| 331 |
+
.psheet-hdr h2 span{
|
| 332 |
+
display:inline-flex;align-items:center;justify-content:center;
|
| 333 |
+
width:32px;height:32px;background:linear-gradient(135deg,#7c3aed,#5b4cf5);
|
| 334 |
+
border-radius:10px;font-size:.9rem}
|
| 335 |
+
.pclose{
|
| 336 |
+
width:32px;height:32px;border:1px solid rgba(255,255,255,.12);
|
| 337 |
+
background:rgba(255,255,255,.06);border-radius:9px;cursor:pointer;
|
| 338 |
+
display:flex;align-items:center;justify-content:center;
|
| 339 |
+
font-size:.8rem;color:rgba(255,255,255,.5);transition:.2s}
|
| 340 |
+
.pclose:hover{background:rgba(239,68,68,.15);border-color:rgba(239,68,68,.4);color:#f87171}
|
| 341 |
+
|
| 342 |
+
/* tab2 */
|
| 343 |
+
.tab2{display:flex;gap:6px;margin:18px 22px 0}
|
| 344 |
+
.tab2 button{
|
| 345 |
+
flex:1;padding:10px;border:1px solid rgba(255,255,255,.08);
|
| 346 |
+
background:rgba(255,255,255,.04);
|
| 347 |
+
color:rgba(255,255,255,.4);font-family:var(--F);font-size:.8rem;font-weight:600;
|
| 348 |
+
border-radius:10px;cursor:pointer;transition:.2s}
|
| 349 |
+
.tab2 button.on{
|
| 350 |
+
border-color:rgba(139,92,246,.5);
|
| 351 |
+
background:linear-gradient(135deg,rgba(124,58,237,.2),rgba(91,76,245,.15));
|
| 352 |
+
color:#c4b5fd}
|
| 353 |
+
|
| 354 |
+
/* package grid */
|
| 355 |
+
.ppkgs{display:grid;grid-template-columns:1fr 1fr;gap:10px;padding:18px 22px 0}
|
| 356 |
+
|
| 357 |
+
.ppkg{
|
| 358 |
+
border:1.5px solid rgba(255,255,255,.07);
|
| 359 |
+
border-radius:18px;padding:18px 14px 14px;cursor:pointer;
|
| 360 |
+
transition:all .22s cubic-bezier(.34,1.1,.64,1);
|
| 361 |
+
background:rgba(255,255,255,.04);
|
| 362 |
+
text-align:center;position:relative;overflow:hidden}
|
| 363 |
+
.ppkg::before{
|
| 364 |
+
content:'';position:absolute;inset:0;
|
| 365 |
+
background:radial-gradient(ellipse at 50% 0%,rgba(139,92,246,.12),transparent 70%);
|
| 366 |
+
opacity:0;transition:.2s}
|
| 367 |
+
.ppkg:hover{border-color:rgba(139,92,246,.4);transform:translateY(-3px);
|
| 368 |
+
box-shadow:0 8px 24px rgba(91,76,245,.2);background:rgba(139,92,246,.08)}
|
| 369 |
+
.ppkg:hover::before{opacity:1}
|
| 370 |
+
.ppkg.sel{
|
| 371 |
+
border-color:rgba(167,139,250,.6);
|
| 372 |
+
background:linear-gradient(145deg,rgba(124,58,237,.18),rgba(91,76,245,.12));
|
| 373 |
+
box-shadow:0 0 0 3px rgba(139,92,246,.15),0 8px 28px rgba(91,76,245,.25)}
|
| 374 |
+
.ppkg.sel::before{opacity:1}
|
| 375 |
+
|
| 376 |
+
.ppkg.pop{border-color:rgba(245,158,11,.35);background:rgba(245,158,11,.05)}
|
| 377 |
+
.ppkg.pop::after{
|
| 378 |
+
content:'๐ฅ Popular';position:absolute;top:-1px;left:50%;transform:translateX(-50%);
|
| 379 |
+
background:linear-gradient(90deg,#f59e0b,#fbbf24);color:#1a0a00;
|
| 380 |
+
font-size:.58rem;font-weight:800;padding:3px 10px;border-radius:0 0 10px 10px;
|
| 381 |
+
white-space:nowrap;letter-spacing:.03em}
|
| 382 |
+
.ppkg.pop:hover{border-color:rgba(245,158,11,.6);box-shadow:0 8px 24px rgba(245,158,11,.2)}
|
| 383 |
+
.ppkg.pop.sel{border-color:#f59e0b;box-shadow:0 0 0 3px rgba(245,158,11,.15),0 8px 28px rgba(245,158,11,.2)}
|
| 384 |
+
|
| 385 |
+
.ppkg-icon{font-size:1.6rem;margin-bottom:6px;display:block;
|
| 386 |
+
filter:drop-shadow(0 2px 8px rgba(0,0,0,.3))}
|
| 387 |
+
.ppkg-c{
|
| 388 |
+
font-size:1.8rem;font-weight:800;
|
| 389 |
+
background:linear-gradient(135deg,#c4b5fd,#a78bfa);
|
| 390 |
+
-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;
|
| 391 |
+
line-height:1;margin-bottom:2px}
|
| 392 |
+
.ppkg.pop .ppkg-c{
|
| 393 |
+
background:linear-gradient(135deg,#fde68a,#fbbf24);
|
| 394 |
+
-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
|
| 395 |
+
.ppkg-u{font-size:.65rem;color:rgba(255,255,255,.4);text-transform:uppercase;letter-spacing:.08em;margin-bottom:8px}
|
| 396 |
+
.ppkg-p{
|
| 397 |
+
font-size:.78rem;font-weight:700;
|
| 398 |
+
background:rgba(255,255,255,.08);border-radius:6px;
|
| 399 |
+
padding:4px 8px;color:rgba(255,255,255,.7);display:inline-block}
|
| 400 |
+
.ppkg.pop .ppkg-p{background:rgba(245,158,11,.15);color:#fbbf24}
|
| 401 |
+
|
| 402 |
+
/* selected badge */
|
| 403 |
+
.ppkg.sel .ppkg-c{background:linear-gradient(135deg,#e9d5ff,#c4b5fd);
|
| 404 |
+
-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
|
| 405 |
+
|
| 406 |
+
/* pinfo */
|
| 407 |
+
.pinfo{
|
| 408 |
+
margin:14px 22px 0;padding:10px 14px;
|
| 409 |
+
background:rgba(139,92,246,.1);border:1px solid rgba(139,92,246,.2);
|
| 410 |
+
border-radius:10px;font-size:.78rem;color:#c4b5fd;
|
| 411 |
+
text-align:center;min-height:36px;display:flex;align-items:center;justify-content:center;
|
| 412 |
+
font-weight:600}
|
| 413 |
+
|
| 414 |
+
/* payment info box */
|
| 415 |
+
.pkbz{
|
| 416 |
+
margin:14px 22px 0;
|
| 417 |
+
background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);
|
| 418 |
+
border-radius:14px;padding:14px;overflow:hidden;position:relative}
|
| 419 |
+
.pkbz::before{
|
| 420 |
+
content:'';position:absolute;top:0;left:0;right:0;height:1px;
|
| 421 |
+
background:linear-gradient(90deg,transparent,rgba(139,92,246,.4),transparent)}
|
| 422 |
+
.pkbz-t{
|
| 423 |
+
font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.1em;
|
| 424 |
+
color:rgba(255,255,255,.35);margin-bottom:10px;display:flex;align-items:center;gap:6px}
|
| 425 |
+
.pkbz-t::after{content:'';flex:1;height:1px;background:rgba(255,255,255,.06)}
|
| 426 |
+
.pkbz-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}
|
| 427 |
.pkbz-row:last-child{margin-bottom:0}
|
| 428 |
+
.pkbz-lbl{font-size:.7rem;color:rgba(255,255,255,.35);font-weight:500}
|
| 429 |
+
.pkbz-val{font-size:.88rem;font-weight:700;color:rgba(255,255,255,.9)}
|
| 430 |
+
.pcopy{
|
| 431 |
+
background:rgba(139,92,246,.12);border:1px solid rgba(139,92,246,.25);
|
| 432 |
+
color:#a78bfa;font-size:.65rem;padding:4px 10px;border-radius:6px;
|
| 433 |
+
cursor:pointer;font-family:var(--F);font-weight:700;transition:.2s}
|
| 434 |
+
.pcopy:hover{background:rgba(139,92,246,.25);border-color:rgba(167,139,250,.5);color:#c4b5fd}
|
| 435 |
+
|
| 436 |
+
/* slip upload */
|
| 437 |
+
.pslip-label{
|
| 438 |
+
font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;
|
| 439 |
+
color:rgba(255,255,255,.4);margin:14px 22px 8px;display:block}
|
| 440 |
+
.pslip-drop{
|
| 441 |
+
margin:0 22px;border:1.5px dashed rgba(139,92,246,.3);border-radius:14px;
|
| 442 |
+
padding:24px 20px;text-align:center;cursor:pointer;transition:.25s;
|
| 443 |
+
position:relative;overflow:hidden;
|
| 444 |
+
background:rgba(139,92,246,.04)}
|
| 445 |
+
.pslip-drop:hover{border-color:rgba(167,139,250,.6);background:rgba(139,92,246,.09)}
|
| 446 |
.pslip-drop input{position:absolute;inset:0;opacity:0;cursor:pointer;width:100%;height:100%}
|
| 447 |
+
.pslip-drop .pi{font-size:2rem;margin-bottom:8px;display:block;
|
| 448 |
+
filter:drop-shadow(0 2px 8px rgba(139,92,246,.4))}
|
| 449 |
+
.pslip-drop .pt{font-size:.75rem;color:rgba(255,255,255,.35);line-height:1.5}
|
| 450 |
+
.pslip-preview{width:100%;max-height:160px;object-fit:contain;border-radius:10px;display:none;margin-top:10px;
|
| 451 |
+
box-shadow:0 4px 20px rgba(0,0,0,.4)}
|
| 452 |
+
|
| 453 |
+
/* submit button */
|
| 454 |
+
.psubmit{
|
| 455 |
+
display:none;width:calc(100% - 44px);margin:16px 22px 0;
|
| 456 |
+
padding:15px;border:none;border-radius:14px;
|
| 457 |
+
background:linear-gradient(135deg,#7c3aed,#5b4cf5,#4f46e5);
|
| 458 |
+
background-size:200% auto;
|
| 459 |
+
color:#fff;font-family:var(--F);font-size:.92rem;font-weight:800;
|
| 460 |
+
cursor:pointer;transition:.3s;letter-spacing:.01em;
|
| 461 |
+
box-shadow:0 4px 24px rgba(91,76,245,.35)}
|
| 462 |
.psubmit.on{display:block}
|
| 463 |
+
.psubmit:hover:not(:disabled){
|
| 464 |
+
background-position:right center;
|
| 465 |
+
transform:translateY(-2px);box-shadow:0 8px 32px rgba(91,76,245,.5)}
|
| 466 |
+
.psubmit:disabled{opacity:.4;cursor:not-allowed;transform:none}
|
| 467 |
+
|
| 468 |
+
/* history tab */
|
| 469 |
+
.ph-card{
|
| 470 |
+
background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.07);
|
| 471 |
+
border-radius:14px;padding:14px 16px;margin-bottom:9px;
|
| 472 |
+
animation:fadeIn .2s ease both}
|
| 473 |
.ph-top{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:8px}
|
| 474 |
+
.ph-coins{font-size:1.05rem;font-weight:800;
|
| 475 |
+
background:linear-gradient(135deg,#c4b5fd,#a78bfa);
|
| 476 |
+
-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
|
| 477 |
+
.ph-badge{font-size:.65rem;padding:4px 10px;border-radius:20px;font-weight:700;letter-spacing:.03em}
|
| 478 |
+
.ph-badge.pending{background:rgba(251,191,36,.12);color:#fbbf24;border:1px solid rgba(251,191,36,.25)}
|
| 479 |
+
.ph-badge.approved{background:rgba(52,211,153,.12);color:#34d399;border:1px solid rgba(52,211,153,.25)}
|
| 480 |
+
.ph-badge.rejected{background:rgba(239,68,68,.12);color:#f87171;border:1px solid rgba(239,68,68,.25)}
|
| 481 |
+
.ph-meta{font-size:.69rem;color:rgba(255,255,255,.3)}
|
| 482 |
+
.ph-empty{text-align:center;padding:36px 0;color:rgba(255,255,255,.25);font-size:.85rem}
|
| 483 |
+
.ph-list-wrap{padding:0 22px}
|
|
|
|
|
|
|
| 484 |
</style>
|
| 485 |
</head>
|
| 486 |
<body>
|
|
|
|
| 717 |
<div id="admmsg" class="admmsg"></div>
|
| 718 |
<button style="padding:8px 14px;background:var(--bg);border:1.5px solid var(--border);border-radius:8px;color:var(--muted);font-family:var(--F);font-size:.75rem;font-weight:600;cursor:pointer;margin-top:4px;transition:.2s" onmouseover="this.style.borderColor='var(--primary)'" onmouseout="this.style.borderColor='var(--border)'" onclick="loadUsers()"><i class="fas fa-users"></i> Load Users</button>
|
| 719 |
<div id="utw" style="overflow-x:auto"></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 720 |
</div>
|
| 721 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 722 |
</div>
|
| 723 |
</div>
|
| 724 |
|
|
|
|
| 853 |
document.getElementById('dav').textContent=dn[0].toUpperCase();
|
| 854 |
document.getElementById('dcoins').textContent=isAdm?'โ':coins;
|
| 855 |
fetch('/api/config').then(r=>r.json()).then(d=>{if(d.admin_tg)window._ADMIN_TG=d.admin_tg;}).catch(()=>{});
|
| 856 |
+
if(isAdm){document.getElementById('admp').style.display='block';document.getElementById('adm-dlink').style.display='flex';document.getElementById('cpill').style.display='none';document.getElementById('dcoins').textContent='โ';}
|
| 857 |
renderV();
|
| 858 |
window.addEventListener('resize',()=>{const v=document.getElementById('pvid');if(v.src&&v.videoWidth)syncCanvasInner(v);});
|
| 859 |
fetch('/api/gemini_voices').then(r=>r.ok?r.json():null).then(d=>{if(d?.ok){GV=d.voices;if(document.getElementById('engine').value==='gemini')renderV();}}).catch(()=>{});
|
|
|
|
| 897 |
}
|
| 898 |
|
| 899 |
function renderPkgs(){
|
| 900 |
+
const icons=['๐ฅ','๐ฅ','๐ฅ','๐'];
|
| 901 |
+
const tiers=['Starter','Basic','Pro','Unlimited'];
|
| 902 |
document.getElementById('ppkgs').innerHTML=_pkgs.map((p,i)=>`
|
| 903 |
<div class="ppkg${i===1?' pop':''}" id="ppkg${i}" onclick="selPkg(${i})">
|
| 904 |
+
<span class="ppkg-icon">${icons[i]||'๐ช'}</span>
|
| 905 |
+
<div style="font-size:.62rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:rgba(255,255,255,.3);margin-bottom:4px">${tiers[i]||''}</div>
|
| 906 |
<div class="ppkg-c">${p.coins}</div>
|
| 907 |
<div class="ppkg-u">Coins</div>
|
| 908 |
<div class="ppkg-p">${p.price}</div>
|
|
|
|
| 914 |
document.getElementById('ppkg'+i).classList.add('sel');
|
| 915 |
_selPkg=_pkgs[i];
|
| 916 |
document.getElementById('pinfo').textContent=`โ
${_selPkg.coins} Coins โ ${_selPkg.price} แแฝแฑแธแแปแแบแแฌแธแแแบ`;
|
| 917 |
+
document.getElementById('pinfo').style.background='rgba(139,92,246,.15)';
|
| 918 |
+
document.getElementById('pinfo').style.borderColor='rgba(167,139,250,.35)';
|
| 919 |
checkPReady();
|
| 920 |
}
|
| 921 |
|
|
|
|
| 980 |
<span class="ph-badge ${p.status}">${lbl[p.status]||p.status}</span>
|
| 981 |
</div>
|
| 982 |
<div class="ph-meta">๐ ${p.id} ยท ${p.created_at.slice(0,16).replace('T',' ')}</div>
|
| 983 |
+
${p.admin_note?`<div style="margin-top:6px;font-size:.72rem;color:rgba(255,255,255,.35);padding:6px 10px;background:rgba(255,255,255,.04);border-radius:7px">๐ ${p.admin_note}</div>`:''}
|
| 984 |
</div>`).join('');
|
| 985 |
}catch(e){ el.innerHTML=`<div class="ph-empty">โ ${e.message}</div>`; }
|
| 986 |
}
|
|
|
|
| 1274 |
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>';}
|
| 1275 |
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();}
|
| 1276 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1277 |
/* โโ UTILS โโ */
|
| 1278 |
function toast(m){const e=document.getElementById('toast');e.textContent=m;e.classList.add('show');setTimeout(()=>e.classList.remove('show'),2800);}
|
| 1279 |
|
|
|
|
| 1299 |
<!-- โโ PAYMENT MODAL โโ -->
|
| 1300 |
<div class="pov" id="pov" onclick="if(event.target===this)closePay()">
|
| 1301 |
<div class="psheet">
|
| 1302 |
+
|
| 1303 |
+
<!-- Header -->
|
| 1304 |
<div class="psheet-hdr">
|
| 1305 |
+
<h2><span>๐ช</span> Coins แแแบแแแบ</h2>
|
| 1306 |
<button class="pclose" onclick="closePay()"><i class="fas fa-times"></i></button>
|
| 1307 |
</div>
|
| 1308 |
+
|
| 1309 |
+
<!-- Tabs -->
|
| 1310 |
<div class="tab2">
|
| 1311 |
<button id="ptab-buy" class="on" onclick="showPayTab('buy')">๐ แแแบแแแบ</button>
|
| 1312 |
<button id="ptab-his" onclick="showPayTab('his')">๐ แแพแแบแแแบแธ</button>
|
|
|
|
| 1314 |
|
| 1315 |
<!-- BUY TAB -->
|
| 1316 |
<div id="ptab-buy-content">
|
| 1317 |
+
|
| 1318 |
+
<!-- Package grid -->
|
| 1319 |
<div class="ppkgs" id="ppkgs"></div>
|
| 1320 |
+
|
| 1321 |
+
<!-- Selected info -->
|
| 1322 |
<div class="pinfo" id="pinfo">Package แแ
แบแแฏ แแฝแฑแธแแปแแบแแซ</div>
|
| 1323 |
+
|
| 1324 |
+
<!-- KBZ Pay -->
|
| 1325 |
<div class="pkbz">
|
| 1326 |
<div class="pkbz-t">๐ฑ KBZ Pay / Wave / AYA Pay</div>
|
| 1327 |
<div class="pkbz-row">
|
| 1328 |
<span class="pkbz-lbl">Name</span>
|
| 1329 |
+
<div style="display:flex;align-items:center;gap:8px">
|
| 1330 |
<span class="pkbz-val" id="p-kbz-name">โ</span>
|
| 1331 |
+
<button class="pcopy" onclick="pcopy('p-kbz-name')"><i class="fas fa-copy"></i> Copy</button>
|
| 1332 |
</div>
|
| 1333 |
</div>
|
| 1334 |
<div class="pkbz-row">
|
| 1335 |
<span class="pkbz-lbl">Phone</span>
|
| 1336 |
+
<div style="display:flex;align-items:center;gap:8px">
|
| 1337 |
<span class="pkbz-val" id="p-kbz-num">โ</span>
|
| 1338 |
+
<button class="pcopy" onclick="pcopy('p-kbz-num')"><i class="fas fa-copy"></i> Copy</button>
|
| 1339 |
</div>
|
| 1340 |
</div>
|
| 1341 |
</div>
|
| 1342 |
+
|
| 1343 |
+
<!-- SCB Bank -->
|
| 1344 |
+
<div class="pkbz" style="margin-bottom:0">
|
| 1345 |
<div class="pkbz-t">๐ฆ SCB Bank</div>
|
| 1346 |
<div class="pkbz-row">
|
| 1347 |
<span class="pkbz-lbl">Name</span>
|
| 1348 |
+
<div style="display:flex;align-items:center;gap:8px">
|
| 1349 |
<span class="pkbz-val" id="p-scb-name">โ</span>
|
| 1350 |
+
<button class="pcopy" onclick="pcopy('p-scb-name')"><i class="fas fa-copy"></i> Copy</button>
|
| 1351 |
</div>
|
| 1352 |
</div>
|
| 1353 |
<div class="pkbz-row">
|
| 1354 |
<span class="pkbz-lbl">Account</span>
|
| 1355 |
+
<div style="display:flex;align-items:center;gap:8px">
|
| 1356 |
<span class="pkbz-val" id="p-scb-num">โ</span>
|
| 1357 |
+
<button class="pcopy" onclick="pcopy('p-scb-num')"><i class="fas fa-copy"></i> Copy</button>
|
| 1358 |
</div>
|
| 1359 |
</div>
|
| 1360 |
</div>
|
| 1361 |
+
|
| 1362 |
+
<!-- Slip upload -->
|
| 1363 |
<span class="pslip-label">๐ธ แแฝแฑแแฝแพแฒ Slip แแฏแถ แแแบแแฑแธแแซ</span>
|
| 1364 |
<div class="pslip-drop" id="pslip-drop">
|
| 1365 |
<input type="file" accept="image/*" id="pslip-input" onchange="handlePSlip(this)">
|
| 1366 |
<div class="pi" id="pslip-icon">๐</div>
|
| 1367 |
+
<div class="pt" id="pslip-txt">Slip แแฏแถแแญแฏ แแฎแแฑแแฌ แแญแแฝแฒแแปแแซ<br><span style="color:rgba(255,255,255,.2);font-size:.7rem">แแญแฏแทแแแฏแแบ แแพแญแแบแแผแฎแธ แแฝแฑแธแแซ</span></div>
|
| 1368 |
<img id="pslip-prev" class="pslip-preview">
|
| 1369 |
</div>
|
| 1370 |
+
|
| 1371 |
+
<!-- Submit -->
|
| 1372 |
+
<button class="psubmit" id="psubmit" onclick="submitPay()" disabled>
|
| 1373 |
+
<i class="fas fa-paper-plane" style="margin-right:8px"></i>Payment แแแบแแฑแธแแซ
|
| 1374 |
+
</button>
|
| 1375 |
+
|
| 1376 |
</div>
|
| 1377 |
|
| 1378 |
<!-- HISTORY TAB -->
|
| 1379 |
<div id="ptab-his-content" style="display:none">
|
| 1380 |
+
<div class="ph-list-wrap">
|
| 1381 |
+
<div id="ph-list"><div class="ph-empty">โณ Loadingโฆ</div></div>
|
| 1382 |
+
</div>
|
| 1383 |
</div>
|
| 1384 |
+
|
| 1385 |
</div>
|
| 1386 |
</div>
|
| 1387 |
|
start.sh
CHANGED
|
@@ -15,6 +15,15 @@ cleanup() {
|
|
| 15 |
}
|
| 16 |
trap cleanup SIGTERM SIGINT
|
| 17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
echo "๐ Starting Gunicorn on port $PORT (workers=$WORKERS, threads=$THREADS)..."
|
| 19 |
gunicorn app:app \
|
| 20 |
--bind "0.0.0.0:$PORT" \
|
|
|
|
| 15 |
}
|
| 16 |
trap cleanup SIGTERM SIGINT
|
| 17 |
|
| 18 |
+
# โโ Clear any stale bot sessions from Telegram โโ
|
| 19 |
+
if [ -n "$TELEGRAM_BOT_TOKEN" ]; then
|
| 20 |
+
echo "๐ Clearing Telegram webhook + stale sessions..."
|
| 21 |
+
# Delete webhook (in case webhook mode was ever used)
|
| 22 |
+
curl -s "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/deleteWebhook?drop_pending_updates=true" > /dev/null || true
|
| 23 |
+
# Small delay to let Telegram close any existing getUpdates long-polls
|
| 24 |
+
sleep 2
|
| 25 |
+
fi
|
| 26 |
+
|
| 27 |
echo "๐ Starting Gunicorn on port $PORT (workers=$WORKERS, threads=$THREADS)..."
|
| 28 |
gunicorn app:app \
|
| 29 |
--bind "0.0.0.0:$PORT" \
|