| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> |
| <title>Telegram Session Manager</title> |
| <style> |
| :root { |
| --bg: #0d1117; |
| --card: #161b22; |
| --border: #30363d; |
| --accent: #58a6ff; |
| --green: #3fb950; |
| --red: #f85149; |
| --text: #e6edf3; |
| --muted: #8b949e; |
| --input: #21262d; |
| } |
| * { box-sizing: border-box; margin: 0; padding: 0; } |
| body { |
| background: var(--bg); |
| color: var(--text); |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; |
| min-height: 100vh; |
| padding: 2rem 1rem; |
| } |
| .container { max-width: 720px; margin: auto; } |
| h1 { font-size: 1.6rem; font-weight: 700; margin-bottom: .25rem; } |
| .subtitle { color: var(--muted); font-size: .9rem; margin-bottom: 2rem; } |
| .card { |
| background: var(--card); |
| border: 1px solid var(--border); |
| border-radius: 12px; |
| padding: 1.5rem; |
| margin-bottom: 1.5rem; |
| } |
| .card h2 { font-size: 1rem; font-weight: 600; margin-bottom: 1rem; color: var(--accent); } |
| .form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .75rem; } |
| .form-grid.full { grid-template-columns: 1fr; } |
| label { display: block; font-size: .8rem; color: var(--muted); margin-bottom: .25rem; } |
| input { |
| width: 100%; |
| background: var(--input); |
| border: 1px solid var(--border); |
| border-radius: 8px; |
| color: var(--text); |
| font-size: .9rem; |
| padding: .55rem .8rem; |
| outline: none; |
| transition: border-color .2s; |
| } |
| input:focus { border-color: var(--accent); } |
| .btn { |
| display: inline-flex; |
| align-items: center; |
| gap: .4rem; |
| padding: .55rem 1.2rem; |
| border-radius: 8px; |
| border: none; |
| font-size: .9rem; |
| font-weight: 600; |
| cursor: pointer; |
| transition: opacity .2s; |
| } |
| .btn:hover { opacity: .85; } |
| .btn:disabled { opacity: .4; cursor: not-allowed; } |
| .btn-primary { background: var(--accent); color: #000; } |
| .btn-green { background: var(--green); color: #000; } |
| .btn-danger { background: var(--red); color: #fff; font-size: .8rem; padding: .35rem .8rem; } |
| .btn-row { margin-top: 1rem; display: flex; gap: .5rem; flex-wrap: wrap; } |
| .alert { |
| padding: .75rem 1rem; |
| border-radius: 8px; |
| font-size: .85rem; |
| margin-top: .75rem; |
| display: none; |
| } |
| .alert.show { display: block; } |
| .alert.success { background: rgba(63,185,80,.15); border: 1px solid var(--green); color: var(--green); } |
| .alert.error { background: rgba(248,81,73,.15); border: 1px solid var(--red); color: var(--red); } |
| .alert.info { background: rgba(88,166,255,.12); border: 1px solid var(--accent); color: var(--accent); } |
| .step { display: none; } |
| .step.active { display: block; } |
| .session-item { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| padding: .75rem; |
| border: 1px solid var(--border); |
| border-radius: 8px; |
| margin-bottom: .5rem; |
| } |
| .session-info small { color: var(--muted); font-size: .78rem; } |
| .badge { |
| display: inline-block; |
| font-size: .7rem; |
| padding: .2rem .55rem; |
| border-radius: 20px; |
| background: rgba(63,185,80,.18); |
| color: var(--green); |
| border: 1px solid var(--green); |
| margin-left: .5rem; |
| } |
| .mono { font-family: monospace; font-size: .82rem; background: var(--input); padding: .5rem; border-radius: 6px; word-break: break-all; margin-top: .5rem; } |
| .spinner { display: inline-block; width: 14px; height: 14px; border: 2px solid rgba(0,0,0,.3); border-top-color: #000; border-radius: 50%; animation: spin .6s linear infinite; } |
| @keyframes spin { to { transform: rotate(360deg); } } |
| .section-title { font-size: .85rem; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; margin-bottom: .75rem; } |
| hr { border: none; border-top: 1px solid var(--border); margin: 1.5rem 0; } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
|
|
| <h1>⚡ Telegram Session Manager</h1> |
| <p class="subtitle">Add Telegram accounts to power your file storage system</p> |
|
|
| |
| <div class="card"> |
| <h2>➕ Add New Session</h2> |
|
|
| |
| <div id="step1" class="step active"> |
| <div class="form-grid"> |
| <div> |
| <label>API ID</label> |
| <input id="apiId" type="number" placeholder="21436919" value="21436919"/> |
| </div> |
| <div> |
| <label>API Hash</label> |
| <input id="apiHash" type="text" placeholder="6f289f8dcc..." value="6f289f8dccefd28e2d8077fd05568004"/> |
| </div> |
| </div> |
| <div class="form-grid full" style="margin-top:.75rem"> |
| <div> |
| <label>Phone Number (with country code)</label> |
| <input id="phone" type="tel" placeholder="+1234567890"/> |
| </div> |
| </div> |
| <div id="alert1" class="alert"></div> |
| <div class="btn-row"> |
| <button class="btn btn-primary" onclick="sendOTP()"> |
| <span id="btn1txt">Send OTP</span> |
| </button> |
| </div> |
| </div> |
|
|
| |
| <div id="step2" class="step"> |
| <p style="color:var(--muted); font-size:.88rem; margin-bottom:.75rem;"> |
| Enter the OTP sent to your Telegram account. |
| </p> |
| <div class="form-grid full"> |
| <div> |
| <label>OTP Code</label> |
| <input id="otp" type="text" placeholder="12345" maxlength="10"/> |
| </div> |
| <div style="margin-top:.6rem"> |
| <label>2FA Password (if enabled)</label> |
| <input id="password2fa" type="password" placeholder="Leave blank if not set"/> |
| </div> |
| </div> |
| <div id="alert2" class="alert"></div> |
| <div class="btn-row"> |
| <button class="btn btn-green" onclick="verifyOTP()"> |
| <span id="btn2txt">Verify & Save</span> |
| </button> |
| <button class="btn" style="background:var(--input); color:var(--text)" onclick="resetFlow()">Back</button> |
| </div> |
| </div> |
|
|
| |
| <div id="step3" class="step"> |
| <div class="alert success show">✅ Session saved successfully! The account is now active.</div> |
| <div> |
| <p class="section-title" style="margin-top:1rem">Your String Session</p> |
| <div id="sessionStringDisplay" class="mono"></div> |
| </div> |
| <div class="btn-row"> |
| <button class="btn btn-primary" onclick="resetFlow()">Add Another Account</button> |
| <button class="btn" style="background:var(--input); color:var(--text)" onclick="loadSessions()">Refresh List ↓</button> |
| </div> |
| </div> |
| </div> |
|
|
| <hr/> |
|
|
| |
| <div class="card"> |
| <h2>📋 Active Sessions</h2> |
| <div id="sessionList"><p style="color:var(--muted); font-size:.9rem">Loading…</p></div> |
| </div> |
|
|
| |
| <div class="card"> |
| <h2>🧪 Quick Upload Test</h2> |
| <p style="color:var(--muted); font-size:.85rem; margin-bottom:.75rem"> |
| Channel ID must be set via env var <code style="background:var(--input);padding:.1rem .3rem;border-radius:4px">CHANNEL_ID</code> |
| or sent as header <code style="background:var(--input);padding:.1rem .3rem;border-radius:4px">X-Channel-Id</code>. |
| </p> |
| <div class="form-grid full"> |
| <div> |
| <label>Channel ID (e.g. -100xxxxxxxxxx)</label> |
| <input id="channelId" type="text" placeholder="-1001234567890"/> |
| </div> |
| </div> |
| <div style="margin-top:.75rem"> |
| <label>Select File</label> |
| <input id="uploadFile" type="file"/> |
| </div> |
| <div id="uploadAlert" class="alert"></div> |
| <div id="uploadResult" style="display:none; margin-top:.75rem"> |
| <p class="section-title">Download Link</p> |
| <div id="downloadLinkBox" class="mono"></div> |
| </div> |
| <div class="btn-row"> |
| <button class="btn btn-primary" onclick="doUpload()">Upload File</button> |
| </div> |
| <div id="uploadProgress" style="display:none; margin-top:.75rem; color:var(--muted); font-size:.85rem"></div> |
| </div> |
|
|
| </div> |
|
|
| <script> |
| let savedPhone = ''; |
| |
| function showAlert(id, msg, type) { |
| const el = document.getElementById(id); |
| el.className = `alert ${type} show`; |
| el.textContent = msg; |
| } |
| |
| function hideAlert(id) { |
| document.getElementById(id).className = 'alert'; |
| } |
| |
| function showStep(n) { |
| document.querySelectorAll('.step').forEach(s => s.classList.remove('active')); |
| document.getElementById('step' + n).classList.add('active'); |
| } |
| |
| function resetFlow() { |
| showStep(1); |
| document.getElementById('otp').value = ''; |
| document.getElementById('password2fa').value = ''; |
| hideAlert('alert1'); |
| hideAlert('alert2'); |
| } |
| |
| async function sendOTP() { |
| const apiId = document.getElementById('apiId').value.trim(); |
| const apiHash = document.getElementById('apiHash').value.trim(); |
| const phone = document.getElementById('phone').value.trim(); |
| if (!apiId || !apiHash || !phone) return showAlert('alert1', 'All fields are required.', 'error'); |
| |
| const btn = document.getElementById('btn1txt'); |
| btn.innerHTML = '<span class="spinner"></span> Sending…'; |
| |
| try { |
| const r = await fetch('/strings/start', { |
| method : 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body : JSON.stringify({ apiId, apiHash, phone }), |
| }); |
| const d = await r.json(); |
| if (!r.ok) throw new Error(d.error); |
| savedPhone = phone; |
| showAlert('alert1', d.message, 'success'); |
| setTimeout(() => showStep(2), 600); |
| } catch (e) { |
| showAlert('alert1', e.message, 'error'); |
| } finally { |
| btn.textContent = 'Send OTP'; |
| } |
| } |
| |
| async function verifyOTP() { |
| const code = document.getElementById('otp').value.trim(); |
| const password = document.getElementById('password2fa').value; |
| if (!code) return showAlert('alert2', 'Enter the OTP code.', 'error'); |
| |
| const btn = document.getElementById('btn2txt'); |
| btn.innerHTML = '<span class="spinner"></span> Verifying…'; |
| |
| try { |
| const r = await fetch('/strings/verify', { |
| method : 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body : JSON.stringify({ phone: savedPhone, code, password }), |
| }); |
| const d = await r.json(); |
| if (!r.ok) throw new Error(d.error); |
| document.getElementById('sessionStringDisplay').textContent = d.sessionString; |
| showStep(3); |
| loadSessions(); |
| } catch (e) { |
| showAlert('alert2', e.message, 'error'); |
| } finally { |
| btn.textContent = 'Verify & Save'; |
| } |
| } |
| |
| async function loadSessions() { |
| const box = document.getElementById('sessionList'); |
| try { |
| const r = await fetch('/strings/list'); |
| const data = await r.json(); |
| if (!data.length) { box.innerHTML = '<p style="color:var(--muted);font-size:.9rem">No sessions found. Add one above.</p>'; return; } |
| box.innerHTML = data.map(s => ` |
| <div class="session-item"> |
| <div class="session-info"> |
| <strong>${s.phone || 'Unknown'}</strong> |
| <span class="badge">active</span><br/> |
| <small>API ID: ${s.apiId} | Added: ${new Date(s.createdAt).toLocaleString()}</small> |
| </div> |
| <button class="btn btn-danger" onclick="deleteSession('${s._id}')">🗑 Remove</button> |
| </div> |
| `).join(''); |
| } catch (e) { |
| box.innerHTML = `<p style="color:var(--red)">${e.message}</p>`; |
| } |
| } |
| |
| async function deleteSession(id) { |
| if (!confirm('Remove this session?')) return; |
| await fetch(`/strings/${id}`, { method: 'DELETE' }); |
| loadSessions(); |
| } |
| |
| async function doUpload() { |
| const fileInput = document.getElementById('uploadFile'); |
| const channelId = document.getElementById('channelId').value.trim(); |
| const alertEl = document.getElementById('uploadAlert'); |
| const progress = document.getElementById('uploadProgress'); |
| const resultEl = document.getElementById('uploadResult'); |
| |
| if (!fileInput.files[0]) return showAlert('uploadAlert', 'Select a file first.', 'error'); |
| hideAlert('uploadAlert'); |
| resultEl.style.display = 'none'; |
| progress.style.display = 'block'; |
| progress.textContent = '⏳ Uploading… (large files may take a while)'; |
| |
| const fd = new FormData(); |
| fd.append('file', fileInput.files[0]); |
| |
| const headers = {}; |
| if (channelId) headers['X-Channel-Id'] = channelId; |
| |
| try { |
| const r = await fetch('/upload', { method: 'POST', body: fd, headers }); |
| const d = await r.json(); |
| if (!r.ok) throw new Error(d.error); |
| progress.style.display = 'none'; |
| resultEl.style.display = 'block'; |
| document.getElementById('downloadLinkBox').innerHTML = |
| `<a href="${d.downloadUrl}" target="_blank" style="color:var(--accent)">${d.downloadUrl}</a> |
| <br/><small style="color:var(--muted)">File: ${d.fileName} | Size: ${(d.totalBytes/1024/1024).toFixed(2)} MB | Chunks: ${d.chunks}</small>`; |
| showAlert('uploadAlert', '✅ Upload complete!', 'success'); |
| } catch (e) { |
| progress.style.display = 'none'; |
| showAlert('uploadAlert', e.message, 'error'); |
| } |
| } |
| |
| |
| loadSessions(); |
| </script> |
| </body> |
| </html> |
|
|