### Setup Status Rendering Source: https://github.com/weebzone/telegram-stremio/blob/master/Backend/fastapi/templates/dashboard.html Fetches and renders the setup progress card, including dynamic list generation and HTML escaping for safety. ```javascript async function loadSetupStatus() { const wrap = document.getElementById('setup-card'); if (!wrap) return; try { const res = await fetch('/api/admin/setup-status'); if (!res.ok) return; const data = await res.json(); const d = data.data || {}; const checks = d.checks || []; if (!checks.length || d.complete) { wrap.innerHTML = ''; return; } const rows = checks.map(c => ` ${escapeHtmlSetup(c.label)} ${escapeHtmlSetup(c.hint)} `).join(''); const pct = Math.round((d.done / d.total) * 100); wrap.innerHTML = `

Finish setting up

${d.done} of ${d.total} steps done — complete these to go live.

${pct}%
${rows}
`; } catch (e) { /* ignore */ } } ``` -------------------------------- ### Clone and Configure Repository Source: https://github.com/weebzone/telegram-stremio/blob/master/README.md Initial setup steps to download the source code and prepare the configuration file. ```bash git clone https://github.com/weebzone/Telegram-Stremio cd Telegram-Stremio cp sample_config.env config.env nano config.env ``` -------------------------------- ### Install Caddy Web Server Source: https://github.com/weebzone/telegram-stremio/blob/master/README.md Commands to install Caddy for automatic HTTPS and reverse proxy functionality. ```bash sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg chmod o+r /etc/apt/sources.list.d/caddy-stable.list sudo apt update sudo apt install caddy ``` -------------------------------- ### Configure Caddyfile Source: https://github.com/weebzone/telegram-stremio/blob/master/README.md Example configuration for the Caddyfile to enable reverse proxying to the application. ```caddy your-domain.com { reverse_proxy localhost:8000 } ``` -------------------------------- ### Example config.env Configuration Source: https://github.com/weebzone/telegram-stremio/blob/master/README.md A template for the required environment variables in the config.env file. ```env API_ID="1234567" API_HASH="abc123def456ghi789jkl012mno345pq" BOT_TOKEN="1234567890:AAEabcdEFGhijkLMnOPqrsTUVwxyz12345" USER_SESSION_STRING="" OWNER_ID="987654321" DATABASE="mongodb+srv://user:pass@cluster0.xxxx.mongodb.net/tracking,mongodb+srv://user:pass@cluster0.xxxx.mongodb.net/storage1" PORT="8000" ``` -------------------------------- ### Movie Filename Examples Source: https://github.com/weebzone/telegram-stremio/blob/master/README.md Standard naming patterns for movies including title, year, and quality. ```text Ghosted 2023 720p WEBRip Hindi x265 HEVC.mkv Oppenheimer.2023.1080p.BluRay.x264.mkv 3 Idiots (2009) 2160p 4K HEVC.mkv ``` -------------------------------- ### TV Show Filename Examples Source: https://github.com/weebzone/telegram-stremio/blob/master/README.md Standard naming patterns for TV shows using SxxExx format. ```text Harikatha Sambhavami Yuge Yuge S01E04 1080p WEB-DL.mkv Loki.S02E03.720p.HEVC.mkv Panchayat S03E05 Hindi 1080p.mkv ``` -------------------------------- ### Initiate Manual Session Source: https://github.com/weebzone/telegram-stremio/blob/master/Backend/fastapi/templates/tools.html Collects form data and sends a POST request to the backend to start a manual session for the selected media. ```javascript async function startManualSession() { if (!msSelected) { showToast('Select a title first.', 'error', 'Session'); return; } const payload = { tmdb_id: msSelected.tmdb_id, db_index: msSelected.db_index, media_type: msSelected.media_type, selected_id: msSelected.selected_id, in_library: msSelected.in_library !== false, }; if (!msSelected.in_library && msSelected.media_type === 'tv') { const rs = document.getElementById('ms-real-season').value.trim(); if (rs !== '') payload.season = rs; } if (msSelected.is_personal) { if (msSelected.media_type === 'tv') { const s = document.getElementById('ms-season').value.trim(); if (s === '') { showToast('Season number is required for personal TV shows.', 'error', 'Session'); return; } payload.season = s; const ep = document.getElementById('ms-episode').value.trim(); if (ep !== '') payload.episode = ep; } const q = document.getElementById('ms-quality').value.trim(); if (q !== '') payload.quality = q; } else if (msSelected.media_type === 'tv') { const rs = document.getElementById('ms-real-season').value.trim(); if (rs !== '') payload.season = rs; } try { const res = await fetch('/api/admin/tools/manual-session', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); const data = await res.json(); if (!res.ok) throw new Error(data.detail || 'Could not start session'); showToast('Session started. ``` -------------------------------- ### Initialization Routines Source: https://github.com/weebzone/telegram-stremio/blob/master/Backend/fastapi/templates/tools.html Initializes session data, channel lists, and starts polling for scan, database, and duplicate statuses if the corresponding UI elements indicate they are running. ```javascript loadManualSession(); loadChannels(); pollScan().then(() => { const pill = document.getElementById('scan-status-pill'); if (pill.classList.contains('status-running')) startScanPolling(); }); pollDbc().then(() => { const pill = document.getElementById('dbc-status-pill'); if (pill.classList.contains('status-running')) startDbcPolling(); }); pollDup().then(() => { const pill = document.getElementById('dup-status-pill'); if (pill.classList.contains('status-running')) startDupPolling(); }); if (document.getElementById('ba-body')) loadBotAdmin(); ``` -------------------------------- ### Manage Manual Sessions Source: https://github.com/weebzone/telegram-stremio/blob/master/Backend/fastapi/templates/tools.html Functions to start, end, load, and render the UI state for manual sessions via the admin API. ```javascript async function endManualSession() { try { const res = await fetch('/api/admin/tools/manual-session', { method: 'DELETE' }); const data = await res.json(); if (!res.ok) throw new Error(data.detail || 'Could not end session'); showToast('Session ended.', 'info', 'Session'); renderManualSession(null); } catch (e) { showToast(e.message || 'Could not end session.', 'error', 'Session'); } } ``` ```javascript function renderManualSession(session) { const pill = document.getElementById('ms-status-pill'); const active = document.getElementById('ms-active'); const setup = document.getElementById('ms-setup'); if (session && session.tmdb_id !== undefined && session.tmdb_id !== null) { pill.className = 'status-pill status-running'; pill.textContent = 'Active'; active.classList.remove('hidden'); setup.classList.add('hidden'); const isTv = session.media_type === 'tv'; document.getElementById('ms-active-title').textContent = `${session.title || 'Title'}${session.year ? ' (' + session.year + ')' : ''}`; let detail = isTv ? 'TV Show' : 'Movie'; if (session.is_personal) { detail += ' · Personal'; if (isTv) { detail += ` · Season ${session.season}`; detail += (session.episode !== null && session.episode !== undefined) ? ` · Episode ${session.episode} (all files → this episode)` : ' · each file → next episode'; } else { detail += ' · each file → a new quality'; } detail += ' · forward to MANUAL channel only'; } else { detail += ' · TMDB/IMDb · auto-detected from each file'; if (isTv && session.season !== null && session.season !== undefined) { detail += ` · fallback Season ${session.season}`; } detail += ' · forward to AUTH or MANUAL channel'; } document.getElementById('ms-active-detail').textContent = detail; } else { pill.className = 'status-pill status-idle'; pill.textContent = 'No session'; active.classList.add('hidden'); setup.classList.add('hidden'); document.getElementById('ms-results').style.display = 'none'; document.getElementById('ms-selected-box').classList.add('hidden'); document.getElementById('ms-search-state').style.display = 'none'; msSelected = null; } } ``` ```javascript async function loadManualSession() { try { const res = await fetch('/api/admin/tools/manual-session'); const data = await res.json(); renderManualSession(data.session); } catch (e) { /* ignore */ } } ``` -------------------------------- ### Initializing the Configuration File Source: https://github.com/weebzone/telegram-stremio/blob/master/README.md Commands to copy the sample configuration file and open it for editing. ```bash cp sample_config.env config.env nano config.env ``` -------------------------------- ### Deploy with Plain Docker Source: https://github.com/weebzone/telegram-stremio/blob/master/README.md Manual deployment method using standard Docker commands. ```bash docker build -t telegram-stremio . docker run -d -p 8000:8000 telegram-stremio ``` -------------------------------- ### Season Pack Filename Examples Source: https://github.com/weebzone/telegram-stremio/blob/master/README.md Naming patterns for files containing multiple episodes or entire seasons. ```text One Piece S01 E01-E13 1080p.mkv → Season 1, Episodes 1–13 Naruto.S02.E14-26.Combined.720p.mkv → Season 2, combined batch Friends S03 1080p.mkv → whole Season 3 (no episode number) ``` -------------------------------- ### Initialize Page Components Source: https://github.com/weebzone/telegram-stremio/blob/master/Backend/fastapi/templates/media_edit.html Sets up event listeners and initializes UI elements like catalog dropdowns and collapsible season content on DOM load. ```javascript document.addEventListener('DOMContentLoaded', () => { const select = document.getElementById('custom-catalog-select'); if (select) select.addEventListener('change', updateCatalogDropdownStatus); loadMediaCatalogDropdown(); loadMediaVisibilityDeps(); document.querySelectorAll('.season-content').forEach((content, index) => { if (index > 0) { content.classList.add('season-collapsed'); content.style.maxHeight = '0px'; content.style.opacity = '0'; const arrow = document.getElementById(`season-arrow-${index}`); if (arrow) arrow.style.transform = 'rotate(-90deg)'; } else { content.style.maxHeight = content.scrollHeight + 'px'; content.style.opacity = '1'; } }); }); ``` -------------------------------- ### Movie Subtitle Filename Examples Source: https://github.com/weebzone/telegram-stremio/blob/master/README.md Naming conventions for movie subtitles using title/year or IMDb ID followed by the language. ```text Wanted 2008 english.srt Wanted 2008 ar.srt tt3326054 arabic.srt tt3326054 eng.srt ``` -------------------------------- ### Deploy with Docker Compose Source: https://github.com/weebzone/telegram-stremio/blob/master/README.md Recommended method for deployment using Docker Compose for easier maintenance. ```bash docker compose up -d ``` -------------------------------- ### TV Episode Subtitle Filename Examples Source: https://github.com/weebzone/telegram-stremio/blob/master/README.md Naming conventions for TV episode subtitles using title/IMDb ID, season/episode markers, and language. ```text Sniffer S01E01 hindi.srt The.Sniffer.S01E01.arabic.srt tt3326054 S01E01 eng.srt tt3326054 S01E01 ar.srt ``` -------------------------------- ### Initialize Subtitle and Tab Logic Source: https://github.com/weebzone/telegram-stremio/blob/master/Backend/fastapi/templates/media_edit.html Handles tab switching based on URL hash and triggers subtitle loading upon DOM content readiness. ```javascript ['details', 'content', 'subtitles', 'catalog', 'rescan']; const hash = (location.hash || '').replace('#', ''); if (valid.includes(hash)) { switchEditTab(hash, false); } else { refreshVisibleSeasonHeights(); } }); document.addEventListener('DOMContentLoaded', () => { const backLink = document.getElementById('back-to-list'); if (backLink) backLink.href = manageListUrl(); if (document.getElementById('subtitle-list')) { loadSubtitleLanguages().then(loadSubtitles); } }); {% endblock %} ``` -------------------------------- ### Load Channels Source: https://github.com/weebzone/telegram-stremio/blob/master/Backend/fastapi/templates/tools.html Fetches and displays the list of configured AUTH channels. ```javascript async function loadChannels() { const list = document.getElementById('channel-list'); try { const res = await fetch('/api/admin/tools/channels'); const data = await res.json(); const channels = (data && data.data) || []; if (!channels.length) { list.innerHTML = `
No AUTH channels configured. ``` -------------------------------- ### Render Bot List and Channel Configuration Source: https://github.com/weebzone/telegram-stremio/blob/master/Backend/fastapi/templates/tools.html Renders a list of bots with main status indicators and handles the display of configured channels or fallback hints. ```javascript block">${escapeHtml(b.name)}${b.is\_main ? ' (main)' : ''} ${b.username ? '@' + escapeHtml(b.username) : escapeHtml(String(b.user\_id))} ").join('') || 'No bots detected.'; const wrap = document.getElementById('ba-channels'); if (!channels.length) { wrap.innerHTML = `
No bot-served channels configured. ``` -------------------------------- ### Media Visibility and Token Picker Source: https://github.com/weebzone/telegram-stremio/blob/master/Backend/fastapi/templates/media_edit.html Functions to load visibility settings and render a UI component for selecting access tokens. ```javascript async function loadMediaVisibilityDeps() { try { const [sRes, tRes] = await Promise.all([ fetch('/api/admin/settings'), fetch('/api/admin/access/tokens') ]); const sData = await sRes.json().catch(() => ({})); const tData = await tRes.json().catch(() => ({})); meSubscriptionOn = !!(sData.settings && sData.settings.subscription); meTokens = (tData.tokens || []).filter(t => t.token); } catch (e) {} const opt = document.querySelector('#media-visibility option[value="public"]'); if (opt) opt.textContent = mePublicLabel(); await loadMediaVisibility(); } ``` ```javascript function renderMediaTokenPicker(selected) { const box = document.getElementById('media-token-picker'); if (!box) return; const sel = new Set(selected || []); if (!meTokens.length) { box.innerHTML = '

No users/tokens found. Create access tokens on the Access page first.

'; return; } ``` -------------------------------- ### Dashboard Initialization Source: https://github.com/weebzone/telegram-stremio/blob/master/Backend/fastapi/templates/admin_dashboard.html Initializes dashboard data fetching and sets up periodic refresh intervals. ```javascript document.addEventListener('DOMContentLoaded', () => { fetchStats(); fetchHealthReport(); fetchAnalytics(); fetchDeadLinks(); setInterval(fetchStats, 5000); // realtime bot load setInterval(fetchHealthReport, 20000); // infra checks (cached server-side) setInterval(fetchAnalytics, 15000); }); {% endblock %} ``` -------------------------------- ### Data Loading Functions Source: https://github.com/weebzone/telegram-stremio/blob/master/Backend/fastapi/templates/access_manage.html Fetches access tokens and subscription plans from the backend API and updates the UI. ```javascript async function loadTokens() { document.getElementById('token-table-body').innerHTML = `Loading…`; try { const resp = await fetch('/api/admin/access/tokens'); const data = await resp.json(); allTokens = data.tokens || []; subEnabled = !!data.subscription; document.getElementById('stat-total').textContent = allTokens.length; document.getElementById('stat-active').textContent = allTokens.filter(t => !t.is_expired).length; document.getElementById('stat-expired').textContent = allTokens.filter(t => t.is_expired).length; currentPage = 1; renderPage(); } catch (err) { document.getElementById('token-table-body').innerHTML = `Error: ${err.message}`; showToast('Failed to load access tokens.', 'error', 'Load Error'); } } async function loadPlans() { try { const resp = await fetch('/api/admin/subscriptions/plans'); if (!resp.ok) return; const result = await resp.json(); const plans = result.data || []; const sel = document.getElementById('assign-plan-select'); if (!sel) return; sel.innerHTML = '' + plans.map(p => ``).join(''); } catch (e) { /* ignore */ } } ``` -------------------------------- ### Rebuild Simple List Source: https://github.com/weebzone/telegram-stremio/blob/master/Backend/fastapi/templates/settings.html Dynamically generates input fields for list-based settings. ```javascript function rebuildSimpleList(containerId, values, inputType) { const container = document.getElementById(containerId); if (!container) return; const listKey = containerId.replace('_items', ''); container.innerHTML = ''; (values || []).forEach(val => { const item = document.createElement('div'); item.className = 'multi-item'; item.innerHTML = ` `; container.appendChild(item); }); } ``` -------------------------------- ### Manage Pagination and Data Fetching Source: https://github.com/weebzone/telegram-stremio/blob/master/Backend/fastapi/templates/subscriptions_manage.html Functions to handle UI state for pagination and initiate subscriber data loading. ```javascript function renderSubPage() { const filtered = getSubFiltered(); const pageSize = parseInt(document.getElementById('sub-page-size').value); const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize)); if (subPage > totalPages) subPage = totalPages; const start = filtered.length ? (subPage - 1) * pageSize : 0; const slice = filtered.slice(start, start + pageSize); document.getElementById('sub-page-info').textContent = filtered.length ? `Showing ${start + 1}–${Math.min(start + pageSize, filtered.length)} of ${filtered.length}` : 'Showing 0 of 0'; document.getElementById('sub-page-num').textContent = `Page ${subPage} / ${totalPages}`; const prevBtn = document.getElementById('sub-btn-prev'); const nextBtn = document.getElementById('sub-btn-next'); prevBtn.disabled = subPage <= 1; nextBtn.disabled = subPage >= totalPages; prevBtn.style.opacity = subPage <= 1 ? '0.45' : '1'; nextBtn.style.opacity = subPage >= totalPages ? '0.45' : '1'; renderDesktopRows(slice); renderMobileCards(slice); } function subChangePage(dir) { const filtered = getSubFiltered(); const pageSize = parseInt(document.getElementById('sub-page-size').value); const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize)); subPage += dir; if (subPage < 1) subPage = 1; if (subPage > totalPages) subPage = totalPages; renderSubPage(); } async function fetchSubscribers() { document.getElementById('subscribers-tbody').innerHTML = ` Loading... `; document.getElementById('subscribers-mobile').innerHTML = `
Loading... ``` -------------------------------- ### Subscription UI Rendering Functions Source: https://github.com/weebzone/telegram-stremio/blob/master/Backend/fastapi/templates/access_manage.html Functions to generate HTML strings for subscription status, addon links, usage limits, and expiry dates based on user token data. ```javascript Owner`; return t.is_expired ? ` Expired` : ` Active`; } function addonHtml(t) { if (!t.has_token) return `
No Token
`; if (t.is_expired && !t.never_expires && !t.is_admin) { return `
${t.addon_url ? `` : ''}
`; } return t.addon_url ? `
` : `
—
`; } function usageHtml(t) { const dl = t.daily_limit_gb ? t.daily_limit_gb + ' GB' : '∞'; const ml = t.monthly_limit_gb ? t.monthly_limit_gb + ' GB' : '∞'; return `
Day: ${fmtBytes(t.daily_bytes)} / ${dl}
Mon: ${fmtBytes(t.monthly_bytes)} / ${ml}
`; } function expiryHtml(t) { if (t.never_expires) return `Never expires`; if (t.expires_at) return `${fmtDate(t.expires_at)}`; return `—`; } ``` -------------------------------- ### Manage Subscription and Token Operations Source: https://github.com/weebzone/telegram-stremio/blob/master/Backend/fastapi/templates/access_manage.html Functions to handle token lifecycle, including creation, limit updates, revocation, and user linking. ```javascript { const name = document.getElementById('create-name').value.trim(); if (!name) { showToast('Token name is required.', 'error', 'Validation'); return; } const payload = { name, daily_limit_gb: Number(document.getElementById('create-daily').value || 0), monthly_limit_gb: Number(document.getElementById('create-monthly').value || 0), subscription_exempt: document.getElementById('create-lifetime').checked, }; try { const resp = await fetch('/api/tokens', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (resp.ok) { showToast('Token created.', 'success', 'Created'); closeModal('create-modal'); loadTokens(); } else { const d = await resp.json(); showToast(d.detail || 'Failed to create token.', 'error', 'Failed'); } } catch (err) { showToast('Error: ' + err.message, 'error', 'Network Error'); } } function openLimitsModal(token, daily, monthly) { document.getElementById('limits-token').value = token; document.getElementById('limits-daily').value = daily || 0; document.getElementById('limits-monthly').value = monthly || 0; openModal('limits-modal'); } async function saveLimits() { const token = document.getElementById('limits-token').value; const payload = { daily_limit_gb: Number(document.getElementById('limits-daily').value || 0), monthly_limit_gb: Number(document.getElementById('limits-monthly').value || 0), }; try { const resp = await fetch('/api/tokens/' + encodeURIComponent(token), { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (resp.ok) { showToast('Limits updated.', 'success', 'Updated'); closeModal('limits-modal'); loadTokens(); } else { showToast('Failed to update limits.', 'error', 'Failed'); } } catch (err) { showToast('Error: ' + err.message, 'error', 'Network Error'); } } async function revokeToken(token) { if (!confirm('Delete this token? The user will lose addon access immediately.')) return; try { const resp = await fetch('/api/admin/access/tokens/' + encodeURIComponent(token), { method: 'DELETE' }); if (resp.ok) { showToast('Token deleted.', 'success', 'Access Updated'); loadTokens(); } else { const d = await resp.json(); showToast(d.detail || 'Failed.', 'error', 'Delete Failed'); } } catch (err) { showToast('Error: ' + err.message, 'error', 'Network Error'); } } async function linkUser(token) { const userId = prompt('Enter the Telegram User ID to link this token to:'); if (!userId || isNaN(parseInt(userId))) { showToast('Invalid user ID.', 'error', 'Invalid Input'); return; } try { const resp = await fetch('/api/admin/access/tokens/' + encodeURIComponent(token) + '/link-user', { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: parseInt(userId) }), }); if (resp.ok) { showToast(`Token linked to user ${userId}.`, 'success', 'User Linked'); loadTokens(); } else { const d = await resp.json(); showToast(d.detail || 'Failed to link.', 'error', 'Link Failed'); } } catch (err) { showToast('Error: ' + err.message, 'error', 'Network Error'); } } function openAssignModal(userId, userName, token) { document.getElementById('assign-user-id').value = userId || ''; document.getElementById('assign-token').value = token || ''; document.getElementById('assign-user-label').textContent = userId ? `User: ${userName} (ID: ${userId})` : `Token: ${userName}`; document.getElementById('assign-custom-days').value = ''; document.getElementById('assign-plan-select').value = ''; //----- With subscription off there are no plans — just set an expiry document.getElementById('assign-plan-field').style.display = subEnabled ? '' : 'none'; document.getElementById('assign-modal-title').textContent = subEnabled ? 'Assign Subscription Plan' : 'Set Expiry'; document.getElementById('assign-days-label').textContent = subEnabled ? 'Or enter custom days' : 'Expires in (days)'; document.getElementById('assign-days-hint').textContent = subEnabled ? 'Custom days override the selected plan. ``` -------------------------------- ### Subscription Management JavaScript Utilities Source: https://github.com/weebzone/telegram-stremio/blob/master/Backend/fastapi/templates/subscriptions_manage.html Helper functions for formatting dates, generating user display names, and rendering status badges. ```javascript let allUsers = []; let subPage = 1; function formatExpiry(expiryValue) { if (!expiryValue) return 'N/A'; const d = new Date(expiryValue); return d.toLocaleDateString('en-IN', { timeZone: 'Asia/Kolkata', day: '2-digit', month: 'short', year: 'numeric' }) + ' ' + d.toLocaleTimeString('en-IN', { timeZone: 'Asia/Kolkata', hour: '2-digit', minute: '2-digit' }); } function getUserDisplayName(u) { let name = u.first_name || ''; if (u.username) name += (name ? ' ' : '') + `(@${u.username})`; if (!name) name = `User ${u._id}`; return name; } function getStatusBadge(status) { return status === 'active' ? ' Active' : ' Expired'; } function getAdminBadge(u) { return u.is_admin ? ' Owner' : ''; } async function fetchPlans() { const container = document.getElementById('plans-container'); container.innerHTML = `
Loading plans...` ``` -------------------------------- ### Refresh Settings from Server Source: https://github.com/weebzone/telegram-stremio/blob/master/Backend/fastapi/templates/settings.html Fetches current settings from the API and triggers the UI render process. ```javascript async function refreshSettingsFromServer() { try { const res = await fetch('/api/admin/settings'); if (!res.ok) return; const data = await res.json(); renderSettings(data.settings || {}); } catch (err) { console.error('Failed to refresh settings UI:', err); } } ``` -------------------------------- ### Manage Subscription Plans Source: https://github.com/weebzone/telegram-stremio/blob/master/Backend/fastapi/templates/subscriptions_manage.html Functions to fetch, render, and manipulate subscription plan data via API calls. ```javascript
`; try { const res = await fetch('/api/admin/subscriptions/plans'); if (!res.ok) throw new Error('Failed to fetch plans'); const data = await res.json(); const plans = data.data || data.plans || []; if (!plans.length) { container.innerHTML = `
No plans found
Create your first subscription plan.
`; return; } container.innerHTML = plans.map(p => `
${p.days} Days Plan
₹${p.price}
Premium Access
`).join(''); } catch (err) { console.error(err); container.innerHTML = `
Failed to load plans.
`; } } function openPlanModal(id = '', days = '', price = '') { document.getElementById('plan-id').value = id; document.getElementById('plan-days').value = days; document.getElementById('plan-price').value = price; document.getElementById('plan-modal-title').innerText = id ? 'Edit Plan' : 'Add New Plan'; const modal = document.getElementById('plan-modal'); modal.classList.remove('hidden'); modal.classList.add('flex'); document.body.classList.add('overflow-hidden'); } function closePlanModal() { const modal = document.getElementById('plan-modal'); modal.classList.add('hidden'); modal.classList.remove('flex'); document.body.classList.remove('overflow-hidden'); } async function submitPlanForm(e) { e.preventDefault(); const id = document.getElementById('plan-id').value; const payload = { days: parseInt(document.getElementById('plan-days').value), price: parseFloat(document.getElementById('plan-price').value) }; try { const res = await fetch(id ? `/api/admin/subscriptions/plans/${id}` : '/api/admin/subscriptions/plans', { method: id ? 'PUT' : 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!res.ok) throw new Error('Failed to save plan'); closePlanModal(); fetchPlans(); } catch (err) { console.error(err); showToast('Failed to save plan.', 'error', 'Save Failed'); } } async function deletePlan(id) { if (!confirm('Delete this plan?')) return; try { const res = await fetch(`/api/admin/subscriptions/plans/${id}`, { method: 'DELETE' }); if (!res.ok) throw new Error('Failed'); fetchPlans(); } catch (err) { console.error(err); showToast('Failed to delete plan.', 'error', 'Delete Failed'); } } ``` -------------------------------- ### Catalog List Rendering Source: https://github.com/weebzone/telegram-stremio/blob/master/Backend/fastapi/templates/custom_catalogs.html Renders the list of available catalogs and manages the expand/collapse UI logic. ```javascript function renderCatalogs() { const box = document.getElementById('catalog-list'); const expandBtn = document.getElementById('catalog-expand-btn'); const wrapper = document.getElementById('catalog-list-wrapper'); if (!catalogs.length) { box.innerHTML = emptyState('✨', 'No custom catalog yet', 'Create your first curated Stremio section above.'); if (expandBtn) expandBtn.classList.add('hidden'); if (wrapper) wrapper.classList.remove('collapsed'); return; } box.innerHTML = catalogs.map(c => { const isActive = c._id === selectedCatalogId; const count = (c.items || []).length; const mode = c.visibility || 'public'; const meta = VIS_META[mode] || VIS_META.public; return `
`; }).join(''); // Manage expand button: only needed when list might overflow (~3+ cards) if (catalogs.length > 3) { if (expandBtn) { expandBtn.classList.remove('hidden'); expandBtn.textContent = catalogListExpanded ? '▴ Collapse list' : '▾ Show all catalogs'; } if (wrapper) wrapper.classList.toggle('collapsed', !catalogListExpanded); } else { if (expandBtn) expandBtn.classList.add('hidden'); if (wrapper) wrapper.classList.remove('collapsed'); } } ``` -------------------------------- ### Copy Stream Link and Token Management Source: https://github.com/weebzone/telegram-stremio/blob/master/Backend/fastapi/templates/media_edit.html Utilities for generating and copying stream links to the clipboard and retrieving API tokens from various storage locations. ```javascript function copyStreamLink(qualityId, fileName) { const base = window.location.origin; const token = getCopylinkToken(); if (!token) { showErrorMessage('No API token found. Please create an API token on the Dashboard page first.'); return; } const encodedName = encodeURIComponent(fileName || 'stream'); const url = `${base}/dl/${token}/${qualityId}/${encodedName}`; navigator.clipboard.writeText(url).then(() => { showSuccessMessage('Stream link copied to clipboard!'); }).catch(() => { prompt('Copy this stream link:', url); }); } ``` ```javascript function getCopylinkToken() { const serverToken = {{ (api_token | tojson) if api_token else 'null' }}; return serverToken || sessionStorage.getItem('api_token') || localStorage.getItem('api_token') || null; } ```