t-storage / strings.html
mrpoddaa's picture
Upload 4 files
5fb533a verified
<!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>
<!-- ── 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} &nbsp;|&nbsp; 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>