Skip to content

Commit 5529054

Browse files
ralyodioclaude
andcommitted
fix(ext): Media tab now fetches real bittorrented favorites (v3.3.4)
The Media tab guessed wrong endpoints (/api/v1/{favorites,livetv,radio,podcasts}) and rendered nothing. Ported the real integration from the feed page (newtab.js): ONE GET /api/v1/favorites → { tv, radio, podcasts, movies }, grouped into Live TV / Podcasts / Radio / Music / Books / Movies & Shows with the correct item shapes (player/url/logo/poster/episodes), a live filter, and the themed /api/player iframe modal (token + theme appended). Ported the .btr-*/.modal CSS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 25ea498 commit 5529054

28 files changed

Lines changed: 161 additions & 94 deletions

File tree

apps/desktop/extensions/ai-sidebar/chat.css

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,34 @@ a { color:var(--cyan); }
6161
.soon { margin:60px auto; max-width:480px; text-align:center; color:var(--muted); padding:24px;
6262
border:1px dashed var(--line); border-radius:12px; }
6363
.hidden { display:none !important; }
64+
65+
/* bittorrented favorites (ported from the feed page) */
66+
#btr-groups { display:flex; flex-direction:column; gap:14px; margin-top:8px; }
67+
.btr-filter { width:100%; max-width:360px; margin:0 0 8px; padding:8px 12px;
68+
background:var(--panel); color:var(--fg); border:1px solid var(--line); border-radius:8px; font:inherit; }
69+
.btr-filter::placeholder { color:var(--muted); }
70+
.btr-group h3 { margin:0 0 8px; font-size:12px; letter-spacing:1px; color:var(--cyan); }
71+
.btr-group .btr-count { color:var(--muted); font-weight:400; letter-spacing:0; }
72+
.btr-items { display:flex; flex-wrap:wrap; gap:10px; }
73+
.btr-item { position:relative; display:flex; flex-direction:column; align-items:center; gap:6px; width:88px;
74+
text-decoration:none; color:var(--fg); }
75+
.btr-item img, .btr-item .btr-ph { width:64px; height:64px; border-radius:10px; border:1px solid var(--line);
76+
background:var(--panel); object-fit:cover; display:grid; place-items:center; font-size:26px; }
77+
.btr-item .btr-label { font-size:11px; text-align:center; line-height:1.2; font-weight:700;
78+
display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
79+
.btr-item .btr-sub { font-size:10px; text-align:center; line-height:1.2; color:var(--muted);
80+
display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
81+
.btr-item:hover .btr-label { color:var(--cyan); }
82+
.btr-item .btr-played { position:absolute; top:-4px; right:8px; width:18px; height:18px; border-radius:50%;
83+
background:#2fd47a; color:#04060c; font-size:11px; font-weight:700; display:grid; place-items:center; }
84+
85+
.modal { position:fixed; inset:0; z-index:100; background:rgba(0,0,0,.82); display:grid; place-items:center; }
86+
.modal[hidden] { display:none; }
87+
.modal-box { position:relative; width:min(92vw,1040px); height:min(82vh,660px); background:#000;
88+
border:1px solid var(--line); border-radius:12px; overflow:hidden; }
89+
.modal-box iframe { width:100%; height:100%; border:0; display:block; }
90+
.modal.audio { place-items:end center; background:rgba(0,0,0,.45); }
91+
.modal.audio .modal-box { width:min(96vw,920px); height:78px; margin-bottom:18px; background:var(--bg); border-radius:12px; }
92+
.modal.audio .modal-close { top:-36px; right:0; }
93+
.modal-close { position:absolute; top:8px; right:8px; z-index:2; width:32px; height:32px;
94+
background:rgba(0,0,0,.6); color:#fff; border:1px solid var(--line); border-radius:8px; cursor:pointer; }

apps/desktop/extensions/ai-sidebar/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"manifest_version": 3,
33
"name": "TronBrowser",
4-
"version": "3.3.3",
4+
"version": "3.3.4",
55
"description": "TronBrowser — privacy-first, AI-native. Branded new tab, private search, CoinPay login, and a bring-your-own-keys AI sidebar.",
66
"icons": {
77
"16": "icons/icon-16.png",

apps/desktop/extensions/ai-sidebar/media.html

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,8 @@
1010
<header class="apphdr">
1111
<img src="icons/favicon.svg" alt="" class="logo" />
1212
<span class="brand">Tron<span class="accent">Browser</span></span>
13-
<nav class="tabs" id="media-tabs">
14-
<button class="tab active" data-tab="favorites">★ Favorites</button>
15-
<button class="tab" data-tab="livetv">📺 Live TV</button>
16-
<button class="tab" data-tab="radio">📻 Radio</button>
17-
<button class="tab" data-tab="podcasts">🎙 Podcasts</button>
18-
</nav>
19-
<span style="flex:1"></span>
13+
<span class="hspace"></span>
14+
<a class="tab" href="https://bittorrented.com" target="_blank" rel="noreferrer">bittorrented.com ↗</a>
2015
<span id="acct" class="muted" style="font-size:12px"></span>
2116
<button id="disc" class="tab hidden" title="Disconnect"></button>
2217
</header>
@@ -25,19 +20,26 @@
2520
<section id="connect" class="panel active">
2621
<div class="connect">
2722
<h2>Connect bittorrented.com</h2>
28-
<p class="muted">Sign in to <strong>bittorrented.com</strong> to stream your favorites, live TV, radio, and podcasts right here. We open the sign-in in a tab and bring the token back to this device.</p>
23+
<p class="muted">Sign in to <strong>bittorrented.com</strong> to stream your Live TV, radio, podcasts, music and movie favorites right here. We open the sign-in in a tab and bring the token back to this device.</p>
2924
<button id="go" class="primary">Connect bittorrented.com</button>
3025
<div id="status" class="status"></div>
3126
</div>
3227
</section>
3328

34-
<!-- content (one panel, swapped by tab) -->
35-
<section id="content" class="panel">
36-
<main class="convo" style="width:100%">
37-
<div id="list" class="msgs" style="gap:8px"></div>
38-
</main>
29+
<!-- content -->
30+
<section id="content" class="panel" style="flex-direction:column; overflow:auto; padding:14px;">
31+
<input id="btr-filter" class="btr-filter" type="search" placeholder="Filter favorites…" autocomplete="off" />
32+
<div id="btr"></div>
3933
</section>
4034

35+
<!-- player modal -->
36+
<div id="player-modal" class="modal" hidden>
37+
<div class="modal-box">
38+
<button id="player-close" class="modal-close" title="Close"></button>
39+
<iframe id="player-frame" allow="autoplay; fullscreen; encrypted-media"></iframe>
40+
</div>
41+
</div>
42+
4143
<script type="module" src="media.js"></script>
4244
</body>
4345
</html>
Lines changed: 90 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,114 @@
11
import { BTR_BASE, getToken, connect, verify, disconnect } from './bittorrented.js';
22

3-
const $ = (id) => document.getElementById(id);
4-
const el = (t, c, x) => { const e = document.createElement(t); if (c) e.className = c; if (x != null) e.textContent = x; return e; };
3+
const el = (id) => document.getElementById(id);
4+
function escapeHtml(s) { const d = document.createElement('div'); d.textContent = s || ''; return d.innerHTML; }
5+
function escAttr(s) { return String(s || '').replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); }
6+
function setStatus(t, k) { const s = el('status'); s.textContent = t; s.className = 'status ' + (k || ''); }
57

6-
let activeTab = 'favorites';
7-
8-
function setStatus(text, kind) { const s = $('status'); s.textContent = text; s.className = 'status ' + (kind || ''); }
8+
let tokenCache = '';
99

1010
function showConnected(email) {
11-
$('connect').classList.remove('active');
12-
$('content').classList.add('active');
13-
$('acct').textContent = email || 'connected';
14-
$('disc').classList.remove('hidden');
15-
loadTab(activeTab);
11+
el('connect').classList.remove('active');
12+
el('content').classList.add('active');
13+
el('acct').textContent = email || 'connected';
14+
el('disc').classList.remove('hidden');
15+
loadFavorites();
1616
}
17-
1817
function showDisconnected() {
19-
$('content').classList.remove('active');
20-
$('connect').classList.add('active');
21-
$('acct').textContent = '';
22-
$('disc').classList.add('hidden');
23-
}
24-
25-
// bittorrented item shapes vary by section — render defensively from common fields.
26-
function fieldOf(o, keys) { for (const k of keys) if (o && o[k] != null && o[k] !== '') return o[k]; return ''; }
27-
28-
function renderItems(items) {
29-
const list = $('list'); list.innerHTML = '';
30-
if (!Array.isArray(items) || items.length === 0) { list.appendChild(el('div', 'muted', 'Nothing here yet.')); return; }
31-
for (const it of items) {
32-
const title = fieldOf(it, ['title', 'name', 'label']) || '(untitled)';
33-
const url = fieldOf(it, ['url', 'stream', 'streamUrl', 'link', 'magnet', 'href']);
34-
const sub = fieldOf(it, ['group', 'category', 'genre', 'author', 'description']);
35-
const row = el('div', 'msg');
36-
const n = el('span', 'n', '▶');
37-
const a = url ? el('a', null, title) : el('span', null, title);
38-
if (url) { a.href = url; a.target = '_blank'; a.rel = 'noreferrer'; }
39-
row.append(n, a);
40-
if (sub) { const s = el('span', 't', ' ' + sub); row.append(s); }
41-
list.appendChild(row);
42-
}
18+
el('content').classList.remove('active');
19+
el('connect').classList.add('active');
20+
el('acct').textContent = '';
21+
el('disc').classList.add('hidden');
4322
}
4423

45-
async function loadTab(tab) {
46-
activeTab = tab;
47-
document.querySelectorAll('#media-tabs .tab').forEach((b) => b.classList.toggle('active', b.dataset.tab === tab));
48-
const list = $('list'); list.innerHTML = ''; list.appendChild(el('div', 'muted', 'Loading…'));
24+
// --- favorites (Live TV / Podcasts / Radio / Music / Books / Movies) ---------
25+
async function loadFavorites() {
4926
const token = await getToken();
50-
// Map UI tabs → bittorrented.com /api/v1 endpoints.
51-
const path = { favorites: 'favorites', livetv: 'livetv', radio: 'radio', podcasts: 'podcasts' }[tab] || 'favorites';
27+
tokenCache = token || '';
28+
el('btr').innerHTML = '<p class="muted">Loading favorites…</p>';
29+
let data;
5230
try {
53-
const r = await fetch(`${BTR_BASE}/api/v1/${path}`, { headers: { authorization: `Bearer ${token}` } });
31+
const r = await fetch(`${BTR_BASE}/api/v1/favorites`, { headers: { authorization: `Bearer ${token}` } });
5432
if (r.status === 401) { showDisconnected(); setStatus('Session expired — connect again.', 'err'); return; }
55-
const data = await r.json().catch(() => ([]));
56-
renderItems(Array.isArray(data) ? data : (data.items || data.results || data[path] || []));
57-
} catch (e) {
58-
list.innerHTML = '';
59-
list.appendChild(el('div', 'muted', 'Could not load ' + path + '. ' + ((e && e.message) || '')));
33+
if (!r.ok) { el('btr').innerHTML = `<p class="muted">Couldn’t load favorites (${r.status}).</p>`; return; }
34+
data = await r.json();
35+
} catch (e) { el('btr').innerHTML = `<p class="muted">Couldn’t load favorites. ${escapeHtml((e && e.message) || '')}</p>`; return; }
36+
37+
const tv = data.tv || [], radio = data.radio || [], pods = data.podcasts || [], movies = data.movies || [];
38+
39+
const tile = (player, url, img, ph, label, sub, played) =>
40+
`<a class="btr-item" data-label="${escAttr(String(label || '').toLowerCase())}" ${player ? `href="#" data-player="${escAttr(player)}"` : `href="${escAttr(url)}" target="_blank" rel="noreferrer"`}>` +
41+
(img ? `<img src="${escAttr(img)}" alt="" loading="lazy" referrerpolicy="no-referrer" />` : `<span class="btr-ph">${ph}</span>`) +
42+
(played ? '<span class="btr-played" title="Played">✓</span>' : '') +
43+
`<span class="btr-label">${escapeHtml(label)}</span>` +
44+
(sub ? `<span class="btr-sub">${escapeHtml(sub)}</span>` : '') + '</a>';
45+
const group = (title, items) => items.length
46+
? `<div class="btr-group" data-group><h3>${title} <span class="btr-count">${items.length}</span></h3><div class="btr-items">${items.join('')}</div></div>` : '';
47+
48+
if (!tv.length && !radio.length && !pods.length && !movies.length) {
49+
el('btr').innerHTML = '<p class="muted">Connected — add Live TV, radio, podcast or movie favorites on bittorrented.com and they’ll show up here.</p>';
50+
return;
6051
}
52+
const music = movies.filter((m) => m.contentType === 'music');
53+
const books = movies.filter((m) => m.contentType === 'book');
54+
const shows = movies.filter((m) => !['music', 'book'].includes(m.contentType));
55+
56+
el('btr').innerHTML = '<div id="btr-groups">' +
57+
group('Live TV', tv.map((c) => tile(c.player, c.url, c.logo, '📺', c.name))) +
58+
group('Podcasts', pods.map((p) => {
59+
const ep = (p.episodes || [])[0];
60+
const played = !!(ep && ep.progress && ep.progress.completed);
61+
const sub = ep ? (played ? `✓ ${ep.title}` : `▶ ${ep.title}`) : 'no recent episodes';
62+
return tile(ep && ep.player, p.url, p.image, '🎙', p.title, sub, played);
63+
})) +
64+
group('Radio', radio.map((s) => tile(s.player, s.url, s.logo, '📻', s.name))) +
65+
group('Music', music.map((m) => tile(m.player, m.url, m.poster, '🎵', m.title))) +
66+
group('Books', books.map((m) => tile(m.player, m.url, m.poster, '📖', m.title))) +
67+
group('Movies & Shows', shows.map((m) => tile(m.player, m.url, m.poster, '🎬', m.title))) +
68+
'</div>';
6169
}
6270

63-
document.querySelectorAll('#media-tabs .tab').forEach((b) => b.addEventListener('click', () => loadTab(b.dataset.tab)));
71+
// live filter
72+
el('btr-filter').addEventListener('input', (e) => {
73+
const q = e.target.value.trim().toLowerCase();
74+
document.querySelectorAll('#btr-groups [data-group]').forEach((g) => {
75+
let any = false;
76+
g.querySelectorAll('.btr-item').forEach((it) => {
77+
const show = !q || (it.getAttribute('data-label') || '').includes(q);
78+
it.style.display = show ? '' : 'none';
79+
if (show) any = true;
80+
});
81+
g.style.display = any ? '' : 'none';
82+
});
83+
});
6484

65-
$('go').addEventListener('click', async () => {
85+
// --- player modal (bittorrented /api/player in a themed iframe) ---------------
86+
function openPlayer(url) {
87+
const sep = url.includes('?') ? '&' : '?';
88+
const tk = tokenCache ? `&token=${encodeURIComponent(tokenCache)}` : '';
89+
const type = (url.match(/[?&]type=([^&]+)/) || [])[1] || '';
90+
const isAudio = ['audio', 'podcast', 'music', 'radio'].includes(type);
91+
el('player-modal').classList.toggle('audio', isAudio);
92+
el('player-frame').src = `${url}${sep}theme=dark&bg=${encodeURIComponent('#05070d')}&accent=${encodeURIComponent('#34e7ff')}${tk}&_=${Date.now()}`;
93+
el('player-modal').hidden = false;
94+
}
95+
function closePlayer() { el('player-modal').hidden = true; el('player-frame').src = 'about:blank'; }
96+
el('btr').addEventListener('click', (e) => {
97+
const a = e.target.closest('[data-player]');
98+
if (a) { e.preventDefault(); openPlayer(a.getAttribute('data-player')); }
99+
});
100+
el('player-close').addEventListener('click', closePlayer);
101+
el('player-modal').addEventListener('click', (e) => { if (e.target === el('player-modal') && !el('player-modal').classList.contains('audio')) closePlayer(); });
102+
103+
// --- connect / disconnect -----------------------------------------------------
104+
el('go').addEventListener('click', async () => {
66105
setStatus('Opening bittorrented.com… finish signing in & approving in the new tab.');
67106
try {
68107
const res = await connect();
69108
if (res.connected) { setStatus('Connected ✓', 'ok'); showConnected(res.email); }
70109
else setStatus('Connection not completed.', 'err');
71110
} catch (e) { setStatus((e && e.message) || 'connect failed', 'err'); }
72111
});
112+
el('disc').addEventListener('click', async () => { await disconnect(); showDisconnected(); });
73113

74-
$('disc').addEventListener('click', async () => { await disconnect(); showDisconnected(); });
75-
76-
// On load: if already connected, jump straight to content.
77-
(async () => {
78-
const res = await verify();
79-
if (res.connected) showConnected(res.email);
80-
})();
114+
(async () => { const res = await verify(); if (res.connected) showConnected(res.email); })();

apps/desktop/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tronbrowser/desktop",
3-
"version": "3.3.3",
3+
"version": "3.3.4",
44
"private": true,
55
"description": "Desktop shell for the TronBrowser Chromium fork",
66
"type": "module",

apps/docs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tronbrowser/docs",
3-
"version": "3.3.3",
3+
"version": "3.3.4",
44
"private": true,
55
"description": "Documentation site",
66
"type": "module",

apps/extensions/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tronbrowser/extensions",
3-
"version": "3.3.3",
3+
"version": "3.3.4",
44
"private": true,
55
"description": "TronBrowser extension store — pay $1, list your MV3 extension (tronbrowser.dev/store)",
66
"type": "module",

apps/mobile/app.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"slug": "tronbrowserdev",
55
"owner": "profullstack",
66
"scheme": "tronbrowser",
7-
"version": "3.3.3",
7+
"version": "3.3.4",
88
"orientation": "portrait",
99
"userInterfaceStyle": "dark",
1010
"platforms": [

apps/mobile/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tronbrowser/mobile",
3-
"version": "3.3.3",
3+
"version": "3.3.4",
44
"private": true,
55
"description": "TronBrowser mobile (Expo / React Native) — Phase 2",
66
"type": "module",

apps/web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tronbrowser/web",
3-
"version": "3.3.3",
3+
"version": "3.3.4",
44
"private": true,
55
"description": "TronBrowser marketing site + web dashboard",
66
"type": "module",

0 commit comments

Comments
 (0)