Download strings.html from mrpoddaa/t-storage: direct link, hf CLI and curl.
- Browser
- Download file 13 kB
-
https://huggingface.co/spaces/mrpoddaa/t-storage/resolve/main/strings.html
- Command line
-
hf download hf://spaces/mrpoddaa/t-storage/strings.html
-
curl -L -o strings.html https://huggingface.co/spaces/mrpoddaa/t-storage/resolve/main/strings.html
13 kB
| <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> | |
| <!-- ── Add Session ── --> | |
| <div class="card"> | |
| <h2>➕ Add New Session</h2> | |
| <!-- Step 1 --> | |
| <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> | |
| <!-- Step 2 --> | |
| <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> | |
| <!-- Step 3 – success --> | |
| <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/> | |
| <!-- ── Session List ── --> | |
| <div class="card"> | |
| <h2>📋 Active Sessions</h2> | |
| <div id="sessionList"><p style="color:var(--muted); font-size:.9rem">Loading…</p></div> | |
| </div> | |
| <!-- ── Quick Upload Test ── --> | |
| <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'); | |
| } | |
| } | |
| // init | |
| loadSessions(); | |
| </script> | |
| </body> | |
| </html> | |