Update app.py
Browse files
app.py
CHANGED
|
@@ -15,13 +15,13 @@ from pymongo import MongoClient
|
|
| 15 |
from pymongo.errors import ConnectionFailure
|
| 16 |
from functools import wraps
|
| 17 |
|
| 18 |
-
|
| 19 |
DB_NAME = "otp_bot"
|
| 20 |
COLLECTION_NAME = "accounts"
|
| 21 |
|
| 22 |
-
if
|
| 23 |
try:
|
| 24 |
-
mongo_client = MongoClient(
|
| 25 |
db = mongo_client[DB_NAME]
|
| 26 |
accounts_collection = db[COLLECTION_NAME]
|
| 27 |
mongo_client.admin.command('ping')
|
|
@@ -30,7 +30,7 @@ if MONGO_URI:
|
|
| 30 |
print(f"❌ MongoDB Connection Failed: {e}")
|
| 31 |
mongo_client = None
|
| 32 |
else:
|
| 33 |
-
print("⚠️
|
| 34 |
mongo_client = None
|
| 35 |
|
| 36 |
print = lambda *args, **kwargs: __builtins__.print(*args, **kwargs, flush=True)
|
|
@@ -152,15 +152,6 @@ app = Flask('')
|
|
| 152 |
app.secret_key = "fourstore-multi-account-secret"
|
| 153 |
sse_clients = []
|
| 154 |
|
| 155 |
-
def owner_required(f):
|
| 156 |
-
@wraps(f)
|
| 157 |
-
def decorated_function(*args, **kwargs):
|
| 158 |
-
owner_id = flask_session.get('owner_id')
|
| 159 |
-
if not owner_id:
|
| 160 |
-
return redirect('/login_owner')
|
| 161 |
-
return f(*args, **kwargs)
|
| 162 |
-
return decorated_function
|
| 163 |
-
|
| 164 |
def get_wib_time():
|
| 165 |
return datetime.now(timezone.utc) + timedelta(hours=7)
|
| 166 |
|
|
@@ -453,55 +444,10 @@ def broadcast_sse(data):
|
|
| 453 |
for q in dead:
|
| 454 |
sse_clients.remove(q)
|
| 455 |
|
| 456 |
-
@app.route('/login_owner', methods=['GET', 'POST'])
|
| 457 |
-
def login_owner():
|
| 458 |
-
if request.method == 'POST':
|
| 459 |
-
owner_id = request.form.get('owner_id')
|
| 460 |
-
if owner_id:
|
| 461 |
-
flask_session['owner_id'] = owner_id
|
| 462 |
-
return redirect('/')
|
| 463 |
-
return '''
|
| 464 |
-
<!DOCTYPE html>
|
| 465 |
-
<html>
|
| 466 |
-
<head>
|
| 467 |
-
<title>Owner Login</title>
|
| 468 |
-
<style>
|
| 469 |
-
body { background: #0a0c10; color: #e4e6eb; font-family: Inter, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
|
| 470 |
-
.login-box { background: #1a1f2c; padding: 40px; border-radius: 24px; border: 1px solid #2d3540; width: 300px; }
|
| 471 |
-
h2 { color: #00f2fe; margin-bottom: 20px; }
|
| 472 |
-
input { width: 100%; padding: 12px; background: #0a0c10; border: 1px solid #2d3540; border-radius: 12px; color: #e4e6eb; margin-bottom: 20px; }
|
| 473 |
-
button { background: #00f2fe; color: #0a0c10; border: none; padding: 12px; border-radius: 12px; font-weight: 600; cursor: pointer; width: 100%; }
|
| 474 |
-
</style>
|
| 475 |
-
</head>
|
| 476 |
-
<body>
|
| 477 |
-
<div class="login-box">
|
| 478 |
-
<h2>Owner Login</h2>
|
| 479 |
-
<form method="post">
|
| 480 |
-
<input type="text" name="owner_id" placeholder="Masukkan Owner ID" required>
|
| 481 |
-
<button type="submit">Login</button>
|
| 482 |
-
</form>
|
| 483 |
-
</div>
|
| 484 |
-
</body>
|
| 485 |
-
</html>
|
| 486 |
-
'''
|
| 487 |
-
|
| 488 |
-
@app.route('/logout_owner')
|
| 489 |
-
def logout_owner():
|
| 490 |
-
flask_session.pop('owner_id', None)
|
| 491 |
-
return redirect('/')
|
| 492 |
-
|
| 493 |
@app.route('/')
|
| 494 |
-
@owner_required
|
| 495 |
def home():
|
| 496 |
-
owner_id = flask_session.get('owner_id')
|
| 497 |
-
|
| 498 |
-
user_accounts = {}
|
| 499 |
-
for acc_id, acc in accounts.items():
|
| 500 |
-
if acc.get("owner_id") == owner_id:
|
| 501 |
-
user_accounts[acc_id] = acc
|
| 502 |
-
|
| 503 |
all_logs = []
|
| 504 |
-
for acc_id, acc in
|
| 505 |
if acc.get("otp_logs"):
|
| 506 |
for log in acc["otp_logs"]:
|
| 507 |
log_copy = log.copy()
|
|
@@ -529,15 +475,16 @@ def home():
|
|
| 529 |
|
| 530 |
all_services = list(set([log.get('service') for log in all_logs if log.get('service')]))
|
| 531 |
|
| 532 |
-
total_otp = sum(len(acc.get('sent_cache', [])) for acc in
|
| 533 |
today_otp = len(all_logs)
|
| 534 |
|
| 535 |
accounts_list = []
|
| 536 |
-
for acc_id, acc in
|
| 537 |
accounts_list.append({
|
| 538 |
"id": acc_id,
|
| 539 |
"username": mask_email(acc.get("username", "Unknown")),
|
| 540 |
"status": acc.get("status", False),
|
|
|
|
| 541 |
"bot_token": "✅" if acc.get("bot_token") else "❌",
|
| 542 |
"chat_id": acc.get("chat_id", "-")[:5] + "..." if acc.get("chat_id") and len(acc.get("chat_id")) > 5 else acc.get("chat_id", "-")
|
| 543 |
})
|
|
@@ -561,7 +508,6 @@ def home():
|
|
| 561 |
.nav-item {{ color: #8b949e; text-decoration: none; padding: 8px 16px; border-radius: 8px; transition: all 0.2s; }}
|
| 562 |
.nav-item:hover {{ background: #2d3540; color: #00f2fe; }}
|
| 563 |
.nav-item.active {{ background: #00f2fe20; color: #00f2fe; border: 1px solid #00f2fe40; }}
|
| 564 |
-
.owner-badge {{ background: #4a3a2d; padding: 8px 16px; border-radius: 100px; font-size: 14px; }}
|
| 565 |
|
| 566 |
.header {{ background: linear-gradient(145deg, #1a1f2c, #0f131c); border-radius: 24px; padding: 28px; margin-bottom: 28px; border: 1px solid #2d3540; }}
|
| 567 |
.header-top {{ display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; flex-wrap: wrap; gap: 15px; }}
|
|
@@ -583,7 +529,7 @@ def home():
|
|
| 583 |
.btn-danger {{ background: #ff4d4d; color: white; }}
|
| 584 |
.btn-small {{ padding: 6px 12px; font-size: 12px; }}
|
| 585 |
|
| 586 |
-
.account-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(
|
| 587 |
.account-card {{ background: #1a1f2c; border-radius: 16px; padding: 20px; border: 1px solid #2d3540; position: relative; }}
|
| 588 |
.account-card.online {{ border-color: #00f2fe; box-shadow: 0 0 15px #00f2fe20; }}
|
| 589 |
.account-status {{ position: absolute; top: 20px; right: 20px; width: 12px; height: 12px; border-radius: 50%; }}
|
|
@@ -600,12 +546,13 @@ def home():
|
|
| 600 |
.clear-btn {{ padding: 8px 16px; background: #2d3a4a; border: none; border-radius: 100px; color: white; font-size: 13px; cursor: pointer; text-decoration: none; }}
|
| 601 |
|
| 602 |
.otp-section {{ background: #0f131c; border-radius: 24px; padding: 28px; border: 1px solid #2d3540; overflow-x: auto; }}
|
| 603 |
-
table {{ width: 100%; border-collapse: collapse; min-width:
|
| 604 |
th {{ text-align: left; padding: 16px 12px; background: #1a1f2c; color: #00f2fe; font-weight: 600; font-size: 13px; text-transform: uppercase; border-bottom: 2px solid #2d3540; }}
|
| 605 |
td {{ padding: 16px 12px; border-bottom: 1px solid #262c38; font-size: 14px; }}
|
| 606 |
.otp-badge {{ background: #002b36; color: #00f2fe; font-family: monospace; font-size: 16px; font-weight: 700; padding: 6px 14px; border-radius: 100px; border: 1px solid #00f2fe40; cursor: pointer; user-select: all; }}
|
| 607 |
.service-badge {{ background: #2d3a4a; padding: 6px 14px; border-radius: 100px; font-size: 12px; font-weight: 600; display: inline-block; }}
|
| 608 |
.account-badge {{ background: #4a3a2d; padding: 4px 10px; border-radius: 20px; font-size: 11px; display: inline-block; }}
|
|
|
|
| 609 |
.whatsapp {{ background: #25D36620; color: #25D366; border: 1px solid #25D36640; }}
|
| 610 |
.telegram {{ background: #26A5E420; color: #26A5E4; border: 1px solid #26A5E440; }}
|
| 611 |
.number {{ font-family: monospace; }}
|
|
@@ -622,9 +569,6 @@ def home():
|
|
| 622 |
<div class="nav-brand">OTP MULTI ACCOUNT</div>
|
| 623 |
<div class="nav-menu">
|
| 624 |
<a href="/" class="nav-item active">🏠 Dashboard</a>
|
| 625 |
-
<a href="/accounts" class="nav-item">📋 My Accounts</a>
|
| 626 |
-
<a href="/logout_owner" class="nav-item">🚪 Logout</a>
|
| 627 |
-
<span class="owner-badge">👤 Owner: {owner_id}</span>
|
| 628 |
</div>
|
| 629 |
</div>
|
| 630 |
|
|
@@ -637,8 +581,8 @@ def home():
|
|
| 637 |
<div class="status-badge">● ONLINE</div>
|
| 638 |
</div>
|
| 639 |
<div class="stats-grid">
|
| 640 |
-
<div class="stat-card"><div class="stat-label">Total Akun</div><div class="stat-value">{len(
|
| 641 |
-
<div class="stat-card"><div class="stat-label">Akun Online</div><div class="stat-value">{sum(1 for a in
|
| 642 |
<div class="stat-card"><div class="stat-label">Total OTP</div><div class="stat-value">{total_otp}</div></div>
|
| 643 |
<div class="stat-card"><div class="stat-label">WIB</div><div class="stat-value" id="wib-time">{get_wib_time().strftime('%H:%M:%S')}</div></div>
|
| 644 |
</div>
|
|
@@ -651,17 +595,14 @@ def home():
|
|
| 651 |
<input type="password" name="password" placeholder="Password" class="form-input" required>
|
| 652 |
<input type="text" name="bot_token" placeholder="Bot Token (opsional)" class="form-input">
|
| 653 |
<input type="text" name="chat_id" placeholder="Chat ID (opsional)" class="form-input">
|
| 654 |
-
<input type="
|
| 655 |
<button type="submit" class="btn">Tambah & Login Otomatis</button>
|
| 656 |
</form>
|
| 657 |
</div>
|
| 658 |
|
| 659 |
-
<
|
| 660 |
-
<h3>📋 Akun Terbaru</h3>
|
| 661 |
-
<a href="/accounts" class="btn btn-small">Lihat Semua Akun →</a>
|
| 662 |
-
</div>
|
| 663 |
<div class="account-grid">
|
| 664 |
-
{
|
| 665 |
</div>
|
| 666 |
|
| 667 |
<div class="search-section">
|
|
@@ -677,12 +618,6 @@ def home():
|
|
| 677 |
{''.join([f'<option value="{s}" {"selected" if filter_service == s else ""}>📱 {s}</option>' for s in sorted(all_services)])}
|
| 678 |
</select>
|
| 679 |
</div>
|
| 680 |
-
<div class="filter-box">
|
| 681 |
-
<select class="filter-select" name="account" onchange="updateFilter('account', this.value)">
|
| 682 |
-
<option value="">👤 Semua Akun</option>
|
| 683 |
-
{''.join([f'<option value="{a["id"]}" {"selected" if filter_account == a["id"] else ""}>👤 {a["username"]}</option>' for a in accounts_list[:10]])}
|
| 684 |
-
</select>
|
| 685 |
-
</div>
|
| 686 |
<a href="/" class="clear-btn">✕ Reset</a>
|
| 687 |
<span class="result-count">📊 {len(all_logs)} hasil</span>
|
| 688 |
</div>
|
|
@@ -694,6 +629,7 @@ def home():
|
|
| 694 |
<tr>
|
| 695 |
<th>WIB</th>
|
| 696 |
<th>Akun</th>
|
|
|
|
| 697 |
<th>Country</th>
|
| 698 |
<th>Number</th>
|
| 699 |
<th>Service</th>
|
|
@@ -744,6 +680,13 @@ def home():
|
|
| 744 |
.then(() => location.reload());
|
| 745 |
}}
|
| 746 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 747 |
function copyOTP(otp) {{
|
| 748 |
navigator.clipboard.writeText(otp).then(() => {{
|
| 749 |
const toast = document.createElement('div');
|
|
@@ -769,7 +712,7 @@ def home():
|
|
| 769 |
"""
|
| 770 |
return html
|
| 771 |
|
| 772 |
-
def
|
| 773 |
if not accounts_list:
|
| 774 |
return '<div style="grid-column:1/-1; text-align:center; padding:40px; color:#8b949e;">Belum ada akun. Tambah akun di atas!</div>'
|
| 775 |
|
|
@@ -780,166 +723,25 @@ def generate_preview_accounts(accounts_list):
|
|
| 780 |
<div class="account-card {status_class}">
|
| 781 |
<div class="account-status status-{status_class}"></div>
|
| 782 |
<h4>{acc['username']}</h4>
|
| 783 |
-
<p style="margin-top:8px; color:#8b949e; font-size:12px;">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 784 |
<p style="color:#8b949e; font-size:12px;">Status: {'🟢 Online' if acc['status'] else '🔴 Offline'}</p>
|
| 785 |
-
|
| 786 |
-
|
| 787 |
-
|
| 788 |
-
|
| 789 |
-
@app.route('/accounts')
|
| 790 |
-
@owner_required
|
| 791 |
-
def accounts_page():
|
| 792 |
-
owner_id = flask_session.get('owner_id')
|
| 793 |
-
|
| 794 |
-
user_accounts = {}
|
| 795 |
-
for acc_id, acc in accounts.items():
|
| 796 |
-
if acc.get("owner_id") == owner_id:
|
| 797 |
-
user_accounts[acc_id] = acc
|
| 798 |
-
|
| 799 |
-
accounts_list = []
|
| 800 |
-
for acc_id, acc in user_accounts.items():
|
| 801 |
-
accounts_list.append({
|
| 802 |
-
"id": acc_id,
|
| 803 |
-
"username": mask_email(acc.get("username", "Unknown")),
|
| 804 |
-
"status": acc.get("status", False),
|
| 805 |
-
"bot_token": acc.get("bot_token", "-")[:10] + "..." if len(acc.get("bot_token", "")) > 10 else acc.get("bot_token", "-"),
|
| 806 |
-
"chat_id": acc.get("chat_id", "-"),
|
| 807 |
-
"created_at": datetime.fromtimestamp(acc.get("created_at", time.time())).strftime("%Y-%m-%d %H:%M") if acc.get("created_at") else "-",
|
| 808 |
-
"last_login": datetime.fromtimestamp(acc.get("last_login", 0)).strftime("%Y-%m-%d %H:%M") if acc.get("last_login") else "-",
|
| 809 |
-
"otp_count": len(acc.get("sent_cache", []))
|
| 810 |
-
})
|
| 811 |
-
|
| 812 |
-
html = f"""
|
| 813 |
-
<!DOCTYPE html>
|
| 814 |
-
<html lang="en">
|
| 815 |
-
<head>
|
| 816 |
-
<meta charset="UTF-8">
|
| 817 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 818 |
-
<title>My Accounts · FOURSTORE</title>
|
| 819 |
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
| 820 |
-
<style>
|
| 821 |
-
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
| 822 |
-
body {{ font-family: 'Inter', sans-serif; background: #0a0c10; color: #e4e6eb; padding: 24px; }}
|
| 823 |
-
.container {{ max-width: 1400px; margin: 0 auto; }}
|
| 824 |
-
|
| 825 |
-
.navbar {{ background: #1a1f2c; border-radius: 16px; padding: 16px 24px; margin-bottom: 24px; display: flex; justify-content: space-between; align-items: center; border: 1px solid #2d3540; }}
|
| 826 |
-
.nav-brand {{ font-size: 20px; font-weight: 700; color: #00f2fe; }}
|
| 827 |
-
.nav-menu {{ display: flex; gap: 20px; align-items: center; }}
|
| 828 |
-
.nav-item {{ color: #8b949e; text-decoration: none; padding: 8px 16px; border-radius: 8px; }}
|
| 829 |
-
.nav-item:hover {{ background: #2d3540; color: #00f2fe; }}
|
| 830 |
-
.nav-item.active {{ background: #00f2fe20; color: #00f2fe; border: 1px solid #00f2fe40; }}
|
| 831 |
-
.owner-badge {{ background: #4a3a2d; padding: 8px 16px; border-radius: 100px; font-size: 14px; }}
|
| 832 |
-
|
| 833 |
-
.header {{ background: linear-gradient(145deg, #1a1f2c, #0f131c); border-radius: 24px; padding: 28px; margin-bottom: 28px; border: 1px solid #2d3540; }}
|
| 834 |
-
.header-top {{ display: flex; justify-content: space-between; align-items: center; }}
|
| 835 |
-
.title h1 {{ font-size: 28px; font-weight: 700; background: linear-gradient(135deg, #00f2fe, #4facfe); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }}
|
| 836 |
-
|
| 837 |
-
.accounts-table {{ background: #0f131c; border-radius: 24px; padding: 28px; border: 1px solid #2d3540; overflow-x: auto; }}
|
| 838 |
-
table {{ width: 100%; border-collapse: collapse; min-width: 1000px; }}
|
| 839 |
-
th {{ text-align: left; padding: 16px 12px; background: #1a1f2c; color: #00f2fe; font-weight: 600; font-size: 13px; text-transform: uppercase; border-bottom: 2px solid #2d3540; }}
|
| 840 |
-
td {{ padding: 16px 12px; border-bottom: 1px solid #262c38; font-size: 14px; }}
|
| 841 |
-
.status-badge {{ display: inline-block; padding: 4px 8px; border-radius: 100px; font-size: 12px; font-weight: 600; }}
|
| 842 |
-
.status-online {{ background: #0a4d3c; color: #a0f0d0; }}
|
| 843 |
-
.status-offline {{ background: #4a2c2c; color: #ffb3b3; }}
|
| 844 |
-
.btn {{ background: #00f2fe; color: #0a0c10; border: none; padding: 6px 12px; border-radius: 8px; font-size: 12px; font-weight: 600; cursor: pointer; text-decoration: none; display: inline-block; }}
|
| 845 |
-
.btn-danger {{ background: #ff4d4d; color: white; }}
|
| 846 |
-
.btn-small {{ padding: 4px 8px; font-size: 11px; }}
|
| 847 |
-
.action-group {{ display: flex; gap: 5px; }}
|
| 848 |
-
</style>
|
| 849 |
-
</head>
|
| 850 |
-
<body>
|
| 851 |
-
<div class="container">
|
| 852 |
-
<div class="navbar">
|
| 853 |
-
<div class="nav-brand">OTP MULTI ACCOUNT</div>
|
| 854 |
-
<div class="nav-menu">
|
| 855 |
-
<a href="/" class="nav-item">🏠 Dashboard</a>
|
| 856 |
-
<a href="/accounts" class="nav-item active">📋 My Accounts</a>
|
| 857 |
-
<a href="/logout_owner" class="nav-item">🚪 Logout</a>
|
| 858 |
-
<span class="owner-badge">👤 Owner: {owner_id}</span>
|
| 859 |
-
</div>
|
| 860 |
-
</div>
|
| 861 |
-
|
| 862 |
-
<div class="header">
|
| 863 |
-
<div class="header-top">
|
| 864 |
-
<div class="title">
|
| 865 |
-
<h1>📋 My Accounts</h1>
|
| 866 |
-
<p style="color:#8b949e; margin-top:8px;">Total: {len(accounts_list)} akun | Online: {sum(1 for a in accounts_list if a['status'])}</p>
|
| 867 |
-
</div>
|
| 868 |
-
<a href="/" class="btn">+ Tambah Akun Baru</a>
|
| 869 |
-
</div>
|
| 870 |
-
</div>
|
| 871 |
-
|
| 872 |
-
<div class="accounts-table">
|
| 873 |
-
<table>
|
| 874 |
-
<thead>
|
| 875 |
-
<tr>
|
| 876 |
-
<th>No</th>
|
| 877 |
-
<th>Username</th>
|
| 878 |
-
<th>Status</th>
|
| 879 |
-
<th>Bot Token</th>
|
| 880 |
-
<th>Chat ID</th>
|
| 881 |
-
<th>OTP Count</th>
|
| 882 |
-
<th>Created At</th>
|
| 883 |
-
<th>Last Login</th>
|
| 884 |
-
<th>Actions</th>
|
| 885 |
-
</tr>
|
| 886 |
-
</thead>
|
| 887 |
-
<tbody>
|
| 888 |
-
{generate_accounts_rows(accounts_list)}
|
| 889 |
-
</tbody>
|
| 890 |
-
</table>
|
| 891 |
</div>
|
| 892 |
</div>
|
| 893 |
-
|
| 894 |
-
<script>
|
| 895 |
-
function loginAccount(accountId) {{
|
| 896 |
-
fetch('/login_account/' + accountId, {{method: 'POST'}})
|
| 897 |
-
.then(() => location.reload());
|
| 898 |
-
}}
|
| 899 |
-
|
| 900 |
-
function logoutAccount(accountId) {{
|
| 901 |
-
fetch('/logout_account/' + accountId, {{method: 'POST'}})
|
| 902 |
-
.then(() => location.reload());
|
| 903 |
-
}}
|
| 904 |
-
</script>
|
| 905 |
-
</body>
|
| 906 |
-
</html>
|
| 907 |
-
"""
|
| 908 |
-
return html
|
| 909 |
-
|
| 910 |
-
def generate_accounts_rows(accounts_list):
|
| 911 |
-
if not accounts_list:
|
| 912 |
-
return '<tr><td colspan="9" style="text-align:center; padding:60px; color:#8b949e;">Belum ada akun. Tambah akun di dashboard!</td></tr>'
|
| 913 |
-
|
| 914 |
-
rows = ""
|
| 915 |
-
for i, acc in enumerate(accounts_list, 1):
|
| 916 |
-
status_class = "status-online" if acc["status"] else "status-offline"
|
| 917 |
-
status_text = "🟢 Online" if acc["status"] else "🔴 Offline"
|
| 918 |
-
|
| 919 |
-
rows += f"""
|
| 920 |
-
<tr>
|
| 921 |
-
<td>{i}</td>
|
| 922 |
-
<td>{acc['username']}</td>
|
| 923 |
-
<td><span class="status-badge {status_class}">{status_text}</span></td>
|
| 924 |
-
<td>{acc['bot_token']}</td>
|
| 925 |
-
<td>{acc['chat_id']}</td>
|
| 926 |
-
<td>{acc['otp_count']}</td>
|
| 927 |
-
<td>{acc['created_at']}</td>
|
| 928 |
-
<td>{acc['last_login']}</td>
|
| 929 |
-
<td>
|
| 930 |
-
<div class="action-group">
|
| 931 |
-
<button onclick="loginAccount('{acc['id']}')" class="btn btn-small">Login</button>
|
| 932 |
-
<button onclick="logoutAccount('{acc['id']}')" class="btn btn-small btn-danger">Logout</button>
|
| 933 |
-
<a href="/delete_account/{acc['id']}" class="btn btn-small btn-danger" onclick="return confirm('Hapus akun ini?')">Hapus</a>
|
| 934 |
-
</div>
|
| 935 |
-
</td>
|
| 936 |
-
</tr>
|
| 937 |
"""
|
| 938 |
-
return
|
| 939 |
|
| 940 |
def generate_otp_rows(logs, search_query):
|
| 941 |
if not logs:
|
| 942 |
-
return '<tr><td colspan="
|
| 943 |
|
| 944 |
rows = ""
|
| 945 |
for log in logs[:100]:
|
|
@@ -949,6 +751,8 @@ def generate_otp_rows(logs, search_query):
|
|
| 949 |
sms = log.get('sms', '')
|
| 950 |
service = log.get('service', 'UNKNOWN')
|
| 951 |
account = log.get('account_username', 'Unknown')
|
|
|
|
|
|
|
| 952 |
|
| 953 |
if search_query:
|
| 954 |
country = re.sub(f'({re.escape(search_query)})', r'<span class="highlight">\1</span>', country, flags=re.I)
|
|
@@ -960,6 +764,7 @@ def generate_otp_rows(logs, search_query):
|
|
| 960 |
<tr class="new-row">
|
| 961 |
<td style="color:#00f2fe;">{log.get('time', '')}</td>
|
| 962 |
<td><span class="account-badge">{account}</span></td>
|
|
|
|
| 963 |
<td>{country}</td>
|
| 964 |
<td><span class="number">{number}</span></td>
|
| 965 |
<td><span class="service-badge {service_class}">{service}</span></td>
|
|
@@ -976,10 +781,10 @@ def add_account_route():
|
|
| 976 |
password = request.form['password']
|
| 977 |
bot_token = request.form.get('bot_token', '')
|
| 978 |
chat_id = request.form.get('chat_id', '')
|
| 979 |
-
owner_id = request.form.get('owner_id')
|
| 980 |
|
| 981 |
if not owner_id:
|
| 982 |
-
owner_id =
|
| 983 |
|
| 984 |
masked = mask_email(username)
|
| 985 |
print(f"\n➕ TAMBAH AKUN BARU: {masked} (ID: {account_id}) (Owner: {owner_id})")
|
|
@@ -1030,14 +835,8 @@ def add_account_route():
|
|
| 1030 |
return redirect('/')
|
| 1031 |
|
| 1032 |
@app.route('/delete_account/<account_id>')
|
| 1033 |
-
@owner_required
|
| 1034 |
def delete_account_route(account_id):
|
| 1035 |
-
owner_id = flask_session.get('owner_id')
|
| 1036 |
-
|
| 1037 |
if account_id in accounts:
|
| 1038 |
-
if accounts[account_id].get("owner_id") != owner_id:
|
| 1039 |
-
return "Unauthorized", 403
|
| 1040 |
-
|
| 1041 |
username = accounts[account_id].get('username', 'Unknown')
|
| 1042 |
masked = mask_email(username)
|
| 1043 |
print(f"\n🗑️ HAPUS AKUN: {masked} (ID: {account_id})")
|
|
@@ -1049,18 +848,13 @@ def delete_account_route(account_id):
|
|
| 1049 |
save_accounts_to_file(accounts)
|
| 1050 |
print(f"✅ Akun dihapus")
|
| 1051 |
|
| 1052 |
-
return redirect(
|
| 1053 |
|
| 1054 |
@app.route('/login_account/<account_id>', methods=['POST'])
|
| 1055 |
-
@owner_required
|
| 1056 |
def login_account_route(account_id):
|
| 1057 |
-
owner_id = flask_session.get('owner_id')
|
| 1058 |
print(f"\n🔔🔔🔔 LOGIN ACCOUNT DIPANGGIL: {account_id} 🔔🔔🔔")
|
| 1059 |
|
| 1060 |
if account_id in accounts:
|
| 1061 |
-
if accounts[account_id].get("owner_id") != owner_id:
|
| 1062 |
-
return "Unauthorized", 403
|
| 1063 |
-
|
| 1064 |
acc = accounts[account_id]
|
| 1065 |
masked = mask_email(acc.get('username', 'Unknown'))
|
| 1066 |
print(f"📋 Data akun: {masked}")
|
|
@@ -1072,7 +866,7 @@ def login_account_route(account_id):
|
|
| 1072 |
acc['password'],
|
| 1073 |
acc.get('bot_token', ''),
|
| 1074 |
acc.get('chat_id', ''),
|
| 1075 |
-
owner_id
|
| 1076 |
)
|
| 1077 |
|
| 1078 |
print(f"📝 Result: {success} - {msg}")
|
|
@@ -1087,17 +881,11 @@ def login_account_route(account_id):
|
|
| 1087 |
else:
|
| 1088 |
print(f"❌ Account ID {account_id} tidak ditemukan!")
|
| 1089 |
|
| 1090 |
-
return redirect(
|
| 1091 |
|
| 1092 |
@app.route('/logout_account/<account_id>', methods=['POST'])
|
| 1093 |
-
@owner_required
|
| 1094 |
def logout_account_route(account_id):
|
| 1095 |
-
owner_id = flask_session.get('owner_id')
|
| 1096 |
-
|
| 1097 |
if account_id in accounts:
|
| 1098 |
-
if accounts[account_id].get("owner_id") != owner_id:
|
| 1099 |
-
return "Unauthorized", 403
|
| 1100 |
-
|
| 1101 |
username = accounts[account_id].get('username', 'Unknown')
|
| 1102 |
masked = mask_email(username)
|
| 1103 |
print(f"\n🔌 LOGOUT: {masked} (ID: {account_id})")
|
|
@@ -1111,7 +899,7 @@ def logout_account_route(account_id):
|
|
| 1111 |
save_accounts_to_file(accounts)
|
| 1112 |
print(f"✅ Logout berhasil")
|
| 1113 |
|
| 1114 |
-
return redirect(
|
| 1115 |
|
| 1116 |
@app.route('/stream')
|
| 1117 |
def stream():
|
|
@@ -1231,8 +1019,8 @@ def main():
|
|
| 1231 |
print(" 📁 Data tersimpan di file accounts.json")
|
| 1232 |
print(" 📋 LOGGING: FULL DETAIL")
|
| 1233 |
print(" 🔒 EMAIL SENSOR: AKTIF")
|
| 1234 |
-
print(" 🤖 AUTO LOGIN: AKTIF
|
| 1235 |
-
print(" 👑 OWNER
|
| 1236 |
print("="*60 + "\n")
|
| 1237 |
|
| 1238 |
for acc_id, acc in accounts.items():
|
|
@@ -1245,7 +1033,7 @@ def main():
|
|
| 1245 |
acc['password'],
|
| 1246 |
acc.get('bot_token', ''),
|
| 1247 |
acc.get('chat_id', ''),
|
| 1248 |
-
acc.get('owner_id', '
|
| 1249 |
)
|
| 1250 |
if success:
|
| 1251 |
thread = Thread(target=run_account_scraper, args=(acc_id,), daemon=True)
|
|
@@ -1260,7 +1048,7 @@ def main():
|
|
| 1260 |
save_accounts_to_file(accounts)
|
| 1261 |
|
| 1262 |
print("\n✅ BOT SIAP! Dashboard: https://fourstore-otp.hf.space")
|
| 1263 |
-
print("
|
| 1264 |
|
| 1265 |
while True:
|
| 1266 |
time.sleep(60)
|
|
|
|
| 15 |
from pymongo.errors import ConnectionFailure
|
| 16 |
from functools import wraps
|
| 17 |
|
| 18 |
+
MONGODB_URI = os.environ.get("MONGODB_URI")
|
| 19 |
DB_NAME = "otp_bot"
|
| 20 |
COLLECTION_NAME = "accounts"
|
| 21 |
|
| 22 |
+
if MONGODB_URI:
|
| 23 |
try:
|
| 24 |
+
mongo_client = MongoClient(MONGODB_URI)
|
| 25 |
db = mongo_client[DB_NAME]
|
| 26 |
accounts_collection = db[COLLECTION_NAME]
|
| 27 |
mongo_client.admin.command('ping')
|
|
|
|
| 30 |
print(f"❌ MongoDB Connection Failed: {e}")
|
| 31 |
mongo_client = None
|
| 32 |
else:
|
| 33 |
+
print("⚠️ MONGODB_URI tidak di set, menggunakan penyimpanan lokal")
|
| 34 |
mongo_client = None
|
| 35 |
|
| 36 |
print = lambda *args, **kwargs: __builtins__.print(*args, **kwargs, flush=True)
|
|
|
|
| 152 |
app.secret_key = "fourstore-multi-account-secret"
|
| 153 |
sse_clients = []
|
| 154 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
def get_wib_time():
|
| 156 |
return datetime.now(timezone.utc) + timedelta(hours=7)
|
| 157 |
|
|
|
|
| 444 |
for q in dead:
|
| 445 |
sse_clients.remove(q)
|
| 446 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 447 |
@app.route('/')
|
|
|
|
| 448 |
def home():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 449 |
all_logs = []
|
| 450 |
+
for acc_id, acc in accounts.items():
|
| 451 |
if acc.get("otp_logs"):
|
| 452 |
for log in acc["otp_logs"]:
|
| 453 |
log_copy = log.copy()
|
|
|
|
| 475 |
|
| 476 |
all_services = list(set([log.get('service') for log in all_logs if log.get('service')]))
|
| 477 |
|
| 478 |
+
total_otp = sum(len(acc.get('sent_cache', [])) for acc in accounts.values())
|
| 479 |
today_otp = len(all_logs)
|
| 480 |
|
| 481 |
accounts_list = []
|
| 482 |
+
for acc_id, acc in accounts.items():
|
| 483 |
accounts_list.append({
|
| 484 |
"id": acc_id,
|
| 485 |
"username": mask_email(acc.get("username", "Unknown")),
|
| 486 |
"status": acc.get("status", False),
|
| 487 |
+
"owner_id": acc.get("owner_id", "-"),
|
| 488 |
"bot_token": "✅" if acc.get("bot_token") else "❌",
|
| 489 |
"chat_id": acc.get("chat_id", "-")[:5] + "..." if acc.get("chat_id") and len(acc.get("chat_id")) > 5 else acc.get("chat_id", "-")
|
| 490 |
})
|
|
|
|
| 508 |
.nav-item {{ color: #8b949e; text-decoration: none; padding: 8px 16px; border-radius: 8px; transition: all 0.2s; }}
|
| 509 |
.nav-item:hover {{ background: #2d3540; color: #00f2fe; }}
|
| 510 |
.nav-item.active {{ background: #00f2fe20; color: #00f2fe; border: 1px solid #00f2fe40; }}
|
|
|
|
| 511 |
|
| 512 |
.header {{ background: linear-gradient(145deg, #1a1f2c, #0f131c); border-radius: 24px; padding: 28px; margin-bottom: 28px; border: 1px solid #2d3540; }}
|
| 513 |
.header-top {{ display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; flex-wrap: wrap; gap: 15px; }}
|
|
|
|
| 529 |
.btn-danger {{ background: #ff4d4d; color: white; }}
|
| 530 |
.btn-small {{ padding: 6px 12px; font-size: 12px; }}
|
| 531 |
|
| 532 |
+
.account-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 20px; margin-bottom: 30px; }}
|
| 533 |
.account-card {{ background: #1a1f2c; border-radius: 16px; padding: 20px; border: 1px solid #2d3540; position: relative; }}
|
| 534 |
.account-card.online {{ border-color: #00f2fe; box-shadow: 0 0 15px #00f2fe20; }}
|
| 535 |
.account-status {{ position: absolute; top: 20px; right: 20px; width: 12px; height: 12px; border-radius: 50%; }}
|
|
|
|
| 546 |
.clear-btn {{ padding: 8px 16px; background: #2d3a4a; border: none; border-radius: 100px; color: white; font-size: 13px; cursor: pointer; text-decoration: none; }}
|
| 547 |
|
| 548 |
.otp-section {{ background: #0f131c; border-radius: 24px; padding: 28px; border: 1px solid #2d3540; overflow-x: auto; }}
|
| 549 |
+
table {{ width: 100%; border-collapse: collapse; min-width: 1200px; }}
|
| 550 |
th {{ text-align: left; padding: 16px 12px; background: #1a1f2c; color: #00f2fe; font-weight: 600; font-size: 13px; text-transform: uppercase; border-bottom: 2px solid #2d3540; }}
|
| 551 |
td {{ padding: 16px 12px; border-bottom: 1px solid #262c38; font-size: 14px; }}
|
| 552 |
.otp-badge {{ background: #002b36; color: #00f2fe; font-family: monospace; font-size: 16px; font-weight: 700; padding: 6px 14px; border-radius: 100px; border: 1px solid #00f2fe40; cursor: pointer; user-select: all; }}
|
| 553 |
.service-badge {{ background: #2d3a4a; padding: 6px 14px; border-radius: 100px; font-size: 12px; font-weight: 600; display: inline-block; }}
|
| 554 |
.account-badge {{ background: #4a3a2d; padding: 4px 10px; border-radius: 20px; font-size: 11px; display: inline-block; }}
|
| 555 |
+
.owner-badge {{ background: #2d4a3a; padding: 4px 10px; border-radius: 20px; font-size: 11px; display: inline-block; }}
|
| 556 |
.whatsapp {{ background: #25D36620; color: #25D366; border: 1px solid #25D36640; }}
|
| 557 |
.telegram {{ background: #26A5E420; color: #26A5E4; border: 1px solid #26A5E440; }}
|
| 558 |
.number {{ font-family: monospace; }}
|
|
|
|
| 569 |
<div class="nav-brand">OTP MULTI ACCOUNT</div>
|
| 570 |
<div class="nav-menu">
|
| 571 |
<a href="/" class="nav-item active">🏠 Dashboard</a>
|
|
|
|
|
|
|
|
|
|
| 572 |
</div>
|
| 573 |
</div>
|
| 574 |
|
|
|
|
| 581 |
<div class="status-badge">● ONLINE</div>
|
| 582 |
</div>
|
| 583 |
<div class="stats-grid">
|
| 584 |
+
<div class="stat-card"><div class="stat-label">Total Akun</div><div class="stat-value">{len(accounts)}</div></div>
|
| 585 |
+
<div class="stat-card"><div class="stat-label">Akun Online</div><div class="stat-value">{sum(1 for a in accounts.values() if a.get('status'))}</div></div>
|
| 586 |
<div class="stat-card"><div class="stat-label">Total OTP</div><div class="stat-value">{total_otp}</div></div>
|
| 587 |
<div class="stat-card"><div class="stat-label">WIB</div><div class="stat-value" id="wib-time">{get_wib_time().strftime('%H:%M:%S')}</div></div>
|
| 588 |
</div>
|
|
|
|
| 595 |
<input type="password" name="password" placeholder="Password" class="form-input" required>
|
| 596 |
<input type="text" name="bot_token" placeholder="Bot Token (opsional)" class="form-input">
|
| 597 |
<input type="text" name="chat_id" placeholder="Chat ID (opsional)" class="form-input">
|
| 598 |
+
<input type="text" name="owner_id" placeholder="Owner ID (opsional)" class="form-input">
|
| 599 |
<button type="submit" class="btn">Tambah & Login Otomatis</button>
|
| 600 |
</form>
|
| 601 |
</div>
|
| 602 |
|
| 603 |
+
<h3 style="margin-bottom: 20px;">📋 Daftar Semua Akun</h3>
|
|
|
|
|
|
|
|
|
|
| 604 |
<div class="account-grid">
|
| 605 |
+
{generate_account_cards(accounts_list)}
|
| 606 |
</div>
|
| 607 |
|
| 608 |
<div class="search-section">
|
|
|
|
| 618 |
{''.join([f'<option value="{s}" {"selected" if filter_service == s else ""}>📱 {s}</option>' for s in sorted(all_services)])}
|
| 619 |
</select>
|
| 620 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 621 |
<a href="/" class="clear-btn">✕ Reset</a>
|
| 622 |
<span class="result-count">📊 {len(all_logs)} hasil</span>
|
| 623 |
</div>
|
|
|
|
| 629 |
<tr>
|
| 630 |
<th>WIB</th>
|
| 631 |
<th>Akun</th>
|
| 632 |
+
<th>Owner</th>
|
| 633 |
<th>Country</th>
|
| 634 |
<th>Number</th>
|
| 635 |
<th>Service</th>
|
|
|
|
| 680 |
.then(() => location.reload());
|
| 681 |
}}
|
| 682 |
|
| 683 |
+
function deleteAccount(accountId) {{
|
| 684 |
+
if (confirm('Hapus akun ini?')) {{
|
| 685 |
+
fetch('/delete_account/' + accountId, {{method: 'GET'}})
|
| 686 |
+
.then(() => location.reload());
|
| 687 |
+
}}
|
| 688 |
+
}}
|
| 689 |
+
|
| 690 |
function copyOTP(otp) {{
|
| 691 |
navigator.clipboard.writeText(otp).then(() => {{
|
| 692 |
const toast = document.createElement('div');
|
|
|
|
| 712 |
"""
|
| 713 |
return html
|
| 714 |
|
| 715 |
+
def generate_account_cards(accounts_list):
|
| 716 |
if not accounts_list:
|
| 717 |
return '<div style="grid-column:1/-1; text-align:center; padding:40px; color:#8b949e;">Belum ada akun. Tambah akun di atas!</div>'
|
| 718 |
|
|
|
|
| 723 |
<div class="account-card {status_class}">
|
| 724 |
<div class="account-status status-{status_class}"></div>
|
| 725 |
<h4>{acc['username']}</h4>
|
| 726 |
+
<p style="margin-top:8px; color:#8b949e; font-size:12px;">
|
| 727 |
+
<span class="owner-badge">👤 Owner: {acc['owner_id']}</span>
|
| 728 |
+
</p>
|
| 729 |
+
<p style="margin-top:8px; color:#8b949e; font-size:12px;">
|
| 730 |
+
Bot: {acc['bot_token']} | Chat: {acc['chat_id']}
|
| 731 |
+
</p>
|
| 732 |
<p style="color:#8b949e; font-size:12px;">Status: {'🟢 Online' if acc['status'] else '🔴 Offline'}</p>
|
| 733 |
+
<div class="account-actions">
|
| 734 |
+
<button onclick="loginAccount('{acc['id']}')" class="btn btn-small">Login</button>
|
| 735 |
+
<button onclick="logoutAccount('{acc['id']}')" class="btn btn-small btn-danger">Logout</button>
|
| 736 |
+
<button onclick="deleteAccount('{acc['id']}')" class="btn btn-small btn-danger">Hapus</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 737 |
</div>
|
| 738 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 739 |
"""
|
| 740 |
+
return html
|
| 741 |
|
| 742 |
def generate_otp_rows(logs, search_query):
|
| 743 |
if not logs:
|
| 744 |
+
return '<tr><td colspan="8" class="empty-state">🔍 Belum ada OTP</td></tr>'
|
| 745 |
|
| 746 |
rows = ""
|
| 747 |
for log in logs[:100]:
|
|
|
|
| 751 |
sms = log.get('sms', '')
|
| 752 |
service = log.get('service', 'UNKNOWN')
|
| 753 |
account = log.get('account_username', 'Unknown')
|
| 754 |
+
account_id = log.get('account_id', '')
|
| 755 |
+
owner_id = accounts.get(account_id, {}).get('owner_id', '-') if account_id else '-'
|
| 756 |
|
| 757 |
if search_query:
|
| 758 |
country = re.sub(f'({re.escape(search_query)})', r'<span class="highlight">\1</span>', country, flags=re.I)
|
|
|
|
| 764 |
<tr class="new-row">
|
| 765 |
<td style="color:#00f2fe;">{log.get('time', '')}</td>
|
| 766 |
<td><span class="account-badge">{account}</span></td>
|
| 767 |
+
<td><span class="owner-badge">{owner_id}</span></td>
|
| 768 |
<td>{country}</td>
|
| 769 |
<td><span class="number">{number}</span></td>
|
| 770 |
<td><span class="service-badge {service_class}">{service}</span></td>
|
|
|
|
| 781 |
password = request.form['password']
|
| 782 |
bot_token = request.form.get('bot_token', '')
|
| 783 |
chat_id = request.form.get('chat_id', '')
|
| 784 |
+
owner_id = request.form.get('owner_id', '')
|
| 785 |
|
| 786 |
if not owner_id:
|
| 787 |
+
owner_id = "default"
|
| 788 |
|
| 789 |
masked = mask_email(username)
|
| 790 |
print(f"\n➕ TAMBAH AKUN BARU: {masked} (ID: {account_id}) (Owner: {owner_id})")
|
|
|
|
| 835 |
return redirect('/')
|
| 836 |
|
| 837 |
@app.route('/delete_account/<account_id>')
|
|
|
|
| 838 |
def delete_account_route(account_id):
|
|
|
|
|
|
|
| 839 |
if account_id in accounts:
|
|
|
|
|
|
|
|
|
|
| 840 |
username = accounts[account_id].get('username', 'Unknown')
|
| 841 |
masked = mask_email(username)
|
| 842 |
print(f"\n🗑️ HAPUS AKUN: {masked} (ID: {account_id})")
|
|
|
|
| 848 |
save_accounts_to_file(accounts)
|
| 849 |
print(f"✅ Akun dihapus")
|
| 850 |
|
| 851 |
+
return redirect('/')
|
| 852 |
|
| 853 |
@app.route('/login_account/<account_id>', methods=['POST'])
|
|
|
|
| 854 |
def login_account_route(account_id):
|
|
|
|
| 855 |
print(f"\n🔔🔔🔔 LOGIN ACCOUNT DIPANGGIL: {account_id} 🔔🔔🔔")
|
| 856 |
|
| 857 |
if account_id in accounts:
|
|
|
|
|
|
|
|
|
|
| 858 |
acc = accounts[account_id]
|
| 859 |
masked = mask_email(acc.get('username', 'Unknown'))
|
| 860 |
print(f"📋 Data akun: {masked}")
|
|
|
|
| 866 |
acc['password'],
|
| 867 |
acc.get('bot_token', ''),
|
| 868 |
acc.get('chat_id', ''),
|
| 869 |
+
acc.get('owner_id', 'default')
|
| 870 |
)
|
| 871 |
|
| 872 |
print(f"📝 Result: {success} - {msg}")
|
|
|
|
| 881 |
else:
|
| 882 |
print(f"❌ Account ID {account_id} tidak ditemukan!")
|
| 883 |
|
| 884 |
+
return redirect('/')
|
| 885 |
|
| 886 |
@app.route('/logout_account/<account_id>', methods=['POST'])
|
|
|
|
| 887 |
def logout_account_route(account_id):
|
|
|
|
|
|
|
| 888 |
if account_id in accounts:
|
|
|
|
|
|
|
|
|
|
| 889 |
username = accounts[account_id].get('username', 'Unknown')
|
| 890 |
masked = mask_email(username)
|
| 891 |
print(f"\n🔌 LOGOUT: {masked} (ID: {account_id})")
|
|
|
|
| 899 |
save_accounts_to_file(accounts)
|
| 900 |
print(f"✅ Logout berhasil")
|
| 901 |
|
| 902 |
+
return redirect('/')
|
| 903 |
|
| 904 |
@app.route('/stream')
|
| 905 |
def stream():
|
|
|
|
| 1019 |
print(" 📁 Data tersimpan di file accounts.json")
|
| 1020 |
print(" 📋 LOGGING: FULL DETAIL")
|
| 1021 |
print(" 🔒 EMAIL SENSOR: AKTIF")
|
| 1022 |
+
print(" 🤖 AUTO LOGIN: AKTIF")
|
| 1023 |
+
print(" 👑 OWNER ID: Input manual di form")
|
| 1024 |
print("="*60 + "\n")
|
| 1025 |
|
| 1026 |
for acc_id, acc in accounts.items():
|
|
|
|
| 1033 |
acc['password'],
|
| 1034 |
acc.get('bot_token', ''),
|
| 1035 |
acc.get('chat_id', ''),
|
| 1036 |
+
acc.get('owner_id', 'default')
|
| 1037 |
)
|
| 1038 |
if success:
|
| 1039 |
thread = Thread(target=run_account_scraper, args=(acc_id,), daemon=True)
|
|
|
|
| 1048 |
save_accounts_to_file(accounts)
|
| 1049 |
|
| 1050 |
print("\n✅ BOT SIAP! Dashboard: https://fourstore-otp.hf.space")
|
| 1051 |
+
print("📝 Owner ID bisa diisi manual saat tambah akun")
|
| 1052 |
|
| 1053 |
while True:
|
| 1054 |
time.sleep(60)
|