anihub-tv 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- Instalação alternativa pelo npm: no TizenBrew, escolha Adicionar módulo → npm e digite `anihub-tv@0.3.1`. Esta distribuição não depende do acesso público ao repositório GitHub.
1
+ Instalação alternativa pelo npm: no TizenBrew, escolha Adicionar módulo → npm e digite `anihub-tv@0.3.2`. Esta distribuição não depende do acesso público ao repositório GitHub.
2
2
 
3
- # AniHub — 0.3.1
3
+ # AniHub — 0.3.2
4
4
 
5
5
  Cliente independente PT-BR para Samsung Tizen 3.0 / Chromium 47, com **AniStream como fonte principal após conectar a conta**. Animes Brasil e TopAnimes continuam disponíveis como alternativas. Código novo, separado de Cinefy e StremioK.
6
6
 
@@ -53,7 +53,7 @@ Para instalar esta versão:
53
53
 
54
54
  1. TizenBrew → Gerenciador de módulos → Adicionar módulo GitHub.
55
55
  2. Use `srwayne15-bot/AniHub@main` no campo GitHub, sem `gh/`. O app aparecerá como **AniHub**.
56
- 3. Confira **v0.3.1** na lateral. O manifesto inicia `service/index.js` no serviço Node do TizenBrew.
56
+ 3. Confira **v0.3.2** na lateral. O manifesto inicia `service/index.js` no serviço Node do TizenBrew.
57
57
  4. Feche e reabra TizenBrew após alterações no serviço. O CDN pode demorar a atualizar arquivos de @main.
58
58
  5. Vincule a conta nessa TV por Quick Connect. A sessão do computador não acompanha o pacote.
59
59
 
@@ -79,7 +79,7 @@ Ferramentas de teste: Acorn e jsdom nas versões de package.json. A TV não inst
79
79
 
80
80
  Edite os arquivos de origem do serviço e execute `node tools/build-service.cjs`: o resultado reúne todos os helpers em `service/index.js`, conforme o carregamento do TizenBrew. Esse arquivo gerado precisa acompanhar a publicação.
81
81
 
82
- **73 verificações automatizadas aprovadas**: 48 gerais e 25 AniStream. Elas cobrem ES5, identidade de episódios, quotas, autenticação simulada, persistência da sessão, HTTP401/403, preferência de idiomas, Quick Connect, proteção de destinos, URLs opacas, conversão e deduplicação de legendas e espera pelo carregamento antes de reproduzir. Rede e mídia são simuladas nessas suítes.
82
+ **73 verificações automatizadas aprovadas**: 51 gerais e 25 AniStream. Elas cobrem ES5, identidade de episódios, quotas, autenticação simulada, persistência da sessão, HTTP401/403, preferência de idiomas, Quick Connect, proteção de destinos, URLs opacas, conversão e deduplicação de legendas e espera pelo carregamento antes de reproduzir. Rede e mídia são simuladas nessas suítes.
83
83
 
84
84
  A autenticação real pelo site, catálogo, restauração de sessão e reprodução 1920×1080 com legenda portuguesa visível foram verificados no Opera do computador. Evidências em `evidence/`; os detalhes e limites constam em [ANISTREAM-0.3.0.md](docs/ANISTREAM-0.3.0.md).
85
85
 
@@ -1,15 +1,15 @@
1
- Instalação alternativa pelo npm: no TizenBrew, escolha Adicionar módulo → npm e digite `anihub-tv@0.3.1`. Esta distribuição não depende do acesso público ao repositório GitHub.
1
+ Instalação alternativa pelo npm: no TizenBrew, escolha Adicionar módulo → npm e digite `anihub-tv@0.3.2`. Esta distribuição não depende do acesso público ao repositório GitHub.
2
2
 
3
3
  # Primeiro teste do AniHub na TV
4
4
 
5
- Versão inicial para este teste: **0.3.1**. Aparelho alvo: Samsung UN55MU7500, Tizen 3.0. Ainda não há resultado de teste físico registrado.
5
+ Versão inicial para este teste: **0.3.2**. Aparelho alvo: Samsung UN55MU7500, Tizen 3.0. Ainda não há resultado de teste físico registrado.
6
6
 
7
7
  ## Instalação
8
8
 
9
9
  1. Abra TizenBrew → Gerenciador de módulos → Adicionar módulo → GitHub.
10
10
  2. Digite **srwayne15-bot/AniHub@main**, sem `gh/` no campo GitHub.
11
11
  3. Confirme e abra **AniHub**. Não remova Anime-TV, Cinefy ou StremioK.
12
- 4. Confira **v0.3.1** na lateral. Se o serviço não responder, feche e reabra o TizenBrew. Não limpe seus dados.
12
+ 4. Confira **v0.3.2** na lateral. Se o serviço não responder, feche e reabra o TizenBrew. Não limpe seus dados.
13
13
 
14
14
  O caminho `gh/srwayne15-bot/AniHub@main` é a forma interna usada pelo CDN/TizenBrew; o campo de módulo GitHub acrescenta o prefixo. Se sua versão do gerenciador tiver um único campo genérico, confira o exemplo mostrado por ela antes de inserir o identificador.
15
15
 
@@ -37,4 +37,7 @@ Tokyo Revengers também foi consultado no computador: a fonte organiza 56 episó
37
37
 
38
38
  Informe **em qual etapa**, a mensagem exibida e se houve **imagem, som e legenda**. Uma foto da tela ajuda; não inclua senha nem token. Não faça reinicialização forçada ou limpeza global de dados antes de relatar o resultado.
39
39
 
40
- Os 73 testes automatizados e a reprodução real no computador não substituem este teste. O carregamento do serviço foi simulado no contrato de VM do TizenBrew, mas não houve execução em Node 4 nativo nem na TV. HLS, WebVTT, TLS, memória e codecs dependem dessa verificação física.
40
+ Os 76 testes automatizados e a reprodução real no computador não substituem este teste. O carregamento do serviço foi simulado no contrato de VM do TizenBrew, mas não houve execução em Node 4 nativo nem na TV. HLS, WebVTT, TLS, memória e codecs dependem dessa verificação física.
41
+
42
+ ## Correção 0.3.2
43
+ A foto do teste físico de 0.3.1 mostrou HTML sem CSS aplicado, com controles do player expostos. A causa do carregamento externo não foi isolada na TV. A nova entrada tv.html incorpora CSS e scripts, eliminando essas requisições na abertura. Há diagnóstico de erro de início sem dados de conta. Os testes simulados validam interface, player oculto e ES5 sem rede de assets; a correção ainda precisa de novo teste físico. Atualize o módulo para anihub-tv@0.3.2 e feche/reabra o TizenBrew. Nenhuma limpeza de armazenamento é necessária.
package/index.html CHANGED
@@ -1,9 +1,9 @@
1
1
  <!doctype html>
2
- <html lang="pt-BR"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="theme-color" content="#10121c"><title>AniHub</title><link rel="stylesheet" href="css/app.css?v=0.3.1"></head>
2
+ <html lang="pt-BR"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="theme-color" content="#10121c"><title>AniHub</title><link rel="stylesheet" href="css/app.css?v=0.3.2"></head>
3
3
  <body><div id="shell">
4
- <aside><div class="brand"><span class="brand-mark">A<span>+</span></span><strong>ANIHUB<br>TV</strong></div><nav id="nav"><button data-action="home" class="nav-item">Início</button><button data-action="search" class="nav-item">Buscar anime</button><button data-action="favorites" class="nav-item">Minha lista</button><button data-action="history" class="nav-item">Continuar assistindo</button><button data-action="account" class="nav-item">Conta AniStream</button><button data-action="settings" class="nav-item">Preferências</button></nav><div class="sidebar-bottom"><span class="live-dot"></span>Cliente independente<br><small>v0.3.1 · Tizen 3.0</small></div></aside>
4
+ <aside><div class="brand"><span class="brand-mark">A<span>+</span></span><strong>ANIHUB<br>TV</strong></div><nav id="nav"><button data-action="home" class="nav-item">Início</button><button data-action="search" class="nav-item">Buscar anime</button><button data-action="favorites" class="nav-item">Minha lista</button><button data-action="history" class="nav-item">Continuar assistindo</button><button data-action="account" class="nav-item">Conta AniStream</button><button data-action="settings" class="nav-item">Preferências</button></nav><div class="sidebar-bottom"><span class="live-dot"></span>Cliente independente<br><small>v0.3.2 · Tizen 3.0</small></div></aside>
5
5
  <main id="main"><header><span id="breadcrumb">Seu próximo episódio</span><button id="refresh" data-action="refresh">Atualizar agora</button></header><div id="status" role="status" aria-live="polite"></div><section id="content"></section></main>
6
6
  </div><div id="dialog" class="hidden" role="dialog" aria-modal="true" aria-labelledby="dialog-title"><div class="dialog-box"><span class="eyebrow">ANIHUB TV</span><h2 id="dialog-title"></h2><p id="dialog-text"></p><div id="dialog-options"></div></div></div>
7
7
  <div id="player" class="hidden"><video id="video" preload="metadata"></video><div id="player-hud"><span class="eyebrow" id="player-state">Preparando vídeo</span><h2 id="player-title"></h2><p id="player-quality"></p><div class="timeline"><div id="progress-bar"></div></div><div id="player-time">00:00 / --:--</div><div class="player-buttons"><button data-action="seek-back">−10 s</button><button data-action="play-pause">Pausar / continuar</button><button data-action="seek-forward">+10 s</button><button data-action="quality">Qualidade</button><button data-action="next">Próximo episódio</button><button data-action="close-player">Voltar ao anime</button></div></div></div>
8
8
  <div id="toast" class="hidden" role="status"></div>
9
- <script src="js/top-provider.js?v=0.3.1"></script><script src="js/core.js?v=0.3.1"></script><script src="js/vendor/hls.light.min.js?v=1.6.13"></script><script src="js/ani-client.js?v=0.3.1"></script><script src="js/app.js?v=0.3.1"></script></body></html>
9
+ <script src="js/top-provider.js?v=0.3.2"></script><script src="js/core.js?v=0.3.2"></script><script src="js/vendor/hls.light.min.js?v=1.6.13"></script><script src="js/ani-client.js?v=0.3.2"></script><script src="js/app.js?v=0.3.2"></script></body></html>
package/js/boot.js ADDED
@@ -0,0 +1,23 @@
1
+ /* ES5 startup diagnostics: no account information, URLs or tokens displayed. */
2
+ (function () {
3
+ 'use strict';
4
+ var completed = false, failed = false, timer;
5
+ function fail(message) {
6
+ var box = document.getElementById('boot-error');
7
+ failed = true;
8
+ if (box) {
9
+ box.textContent = 'AniHub: falha na abertura\n' + message + '\nFeche e abra o TizenBrew. Se continuar, envie uma foto desta mensagem.';
10
+ box.style.display = 'block';
11
+ }
12
+ }
13
+ window.addEventListener('error', function (event) {
14
+ if (!completed) { fail('Código de início: JS' + (Number(event.lineno) || 0)); }
15
+ });
16
+ timer = setTimeout(function () { if (!completed) { fail('Código de início: TEMPO'); } }, 12000);
17
+ window.AniHubBoot = { ready: function () {
18
+ clearTimeout(timer);
19
+ if (!window.ABTVApp || !window.ABTVAniClient || !window.ABTV) { fail('Código de início: MODULOS'); return; }
20
+ completed = true;
21
+ if (!failed) { document.getElementById('boot-error').style.display = 'none'; }
22
+ } };
23
+ }());
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "anihub-tv",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "appName": "AniHub",
5
5
  "description": "Cliente independente ES5 para AniStream, Animes Brasil e TopAnimes no Tizen 3.0",
6
6
  "packageType": "app",
7
- "appPath": "index.html",
7
+ "appPath": "tv.html",
8
8
  "serviceFile": "service/index.js",
9
9
  "keys": [
10
10
  "MediaPlay",
@@ -16,7 +16,8 @@
16
16
  ],
17
17
  "scripts": {
18
18
  "start": "node service/index.js",
19
- "test": "node tests/run.cjs && node tests/anistream.cjs"
19
+ "test": "node tests/run.cjs && node tests/anistream.cjs",
20
+ "build": "node tools/build-service.cjs && node tools/build-tv.cjs"
20
21
  },
21
22
  "devDependencies": {
22
23
  "acorn": "8.19.0",
@@ -39,13 +39,13 @@ var ABTVAniService = (function () {
39
39
  function create(options) {
40
40
  options = options || {};
41
41
  var https = options.https || require('https'), url = require('url'), crypto = require('crypto'), tickets = {}, quick = {}, revoked = {};
42
- function auth(s) { return 'MediaBrowser Client="AniHub", Device="Samsung Tizen", DeviceId="' + encodeURIComponent(s.deviceId) + '", Version="0.3.1"' + (s.token ? ', Token="' + encodeURIComponent(s.token) + '"' : ''); }
42
+ function auth(s) { return 'MediaBrowser Client="AniHub", Device="Samsung Tizen", DeviceId="' + encodeURIComponent(s.deviceId) + '", Version="0.3.2"' + (s.token ? ', Token="' + encodeURIComponent(s.token) + '"' : ''); }
43
43
  function error(code, message, status) { var e = new Error(message); e.code = code; e.status = status || 502; return e; }
44
44
  function request(s, method, path, body, cb) {
45
45
  if (options.request) { options.request(s, method, path, body, cb); return; }
46
46
  var opts = url.parse(BASE + path), done = false, timer;
47
47
  var data = body === undefined ? null : JSON.stringify(body);
48
- opts.method = method; opts.headers = { Authorization: auth(s), 'User-Agent': 'AniHub/0.3.1 (Samsung Tizen 3.0; Jellyfin compatible client)', 'Accept-Encoding': 'identity', Accept: 'application/json' };
48
+ opts.method = method; opts.headers = { Authorization: auth(s), 'User-Agent': 'AniHub/0.3.2 (Samsung Tizen 3.0; Jellyfin compatible client)', 'Accept-Encoding': 'identity', Accept: 'application/json' };
49
49
  if (data !== null) { opts.headers['Content-Type'] = 'application/json'; opts.headers['Content-Length'] = Buffer.byteLength(data); }
50
50
  function finish(e, value) { if (done) { return; } done = true; clearTimeout(timer); cb(e, value); }
51
51
  var req = https.request(opts, function (res) {
@@ -153,7 +153,7 @@ var ABTVAniService = (function () {
153
153
  if (t.kind === 'image') { address = '/Items/' + t.data.id + '/Images/Primary?maxWidth=480&quality=85'; }
154
154
  else if (t.kind === 'subtitle') { address = t.data.path; }
155
155
  else { address = query.part === undefined ? t.data.path : /^\d+$/.test(String(query.part)) ? t.data.parts[Number(query.part)] : ''; if (!address || !safeMedia(address)) { res.writeHead(403); res.end(); return; } }
156
- var opts = url.parse(BASE + relative(address)); opts.headers = { Authorization: auth(t.session), 'User-Agent': 'AniHub/0.3.1 (Samsung Tizen 3.0; Jellyfin compatible client)', 'Accept-Encoding': 'identity' };
156
+ var opts = url.parse(BASE + relative(address)); opts.headers = { Authorization: auth(t.session), 'User-Agent': 'AniHub/0.3.2 (Samsung Tizen 3.0; Jellyfin compatible client)', 'Accept-Encoding': 'identity' };
157
157
  upstream = https.get(opts, function (r) {
158
158
  if (r.statusCode !== 200 && r.statusCode !== 206) { r.resume(); res.writeHead(r.statusCode === 401 ? 401 : 502); res.end('Origem indisponível: HTTP ' + r.statusCode + '.'); return; }
159
159
  if (t.kind === 'subtitle') { var subtitleParts = [], subtitleSize = 0; r.on('data', function (b) { subtitleSize += b.length; if (subtitleSize > 16 * 1024 * 1024) { upstream.destroy(); } else { subtitleParts.push(b); } }); r.on('end', function () { if (res.finished || done) { return; } try { var text = toVtt(Buffer.concat(subtitleParts).toString('utf8'), t.data.codec); t.data.vtt = text; res.writeHead(200, { 'Content-Type': 'text/vtt; charset=utf-8', 'Cache-Control': 'no-store' }); res.end(text); } catch (ignore) { res.writeHead(502); res.end('Legenda WebVTT indisponível.'); } }); r.on('error', function (e) { if (!res.headersSent) { res.writeHead(502); res.end('Falha na transferência da legenda. Atualize as fontes.'); } else { res.destroy(); } }); return; }
package/service/index.js CHANGED
@@ -128,13 +128,13 @@ var ABTVAniService = (function () {
128
128
  function create(options) {
129
129
  options = options || {};
130
130
  var https = options.https || require('https'), url = require('url'), crypto = require('crypto'), tickets = {}, quick = {}, revoked = {};
131
- function auth(s) { return 'MediaBrowser Client="AniHub", Device="Samsung Tizen", DeviceId="' + encodeURIComponent(s.deviceId) + '", Version="0.3.1"' + (s.token ? ', Token="' + encodeURIComponent(s.token) + '"' : ''); }
131
+ function auth(s) { return 'MediaBrowser Client="AniHub", Device="Samsung Tizen", DeviceId="' + encodeURIComponent(s.deviceId) + '", Version="0.3.2"' + (s.token ? ', Token="' + encodeURIComponent(s.token) + '"' : ''); }
132
132
  function error(code, message, status) { var e = new Error(message); e.code = code; e.status = status || 502; return e; }
133
133
  function request(s, method, path, body, cb) {
134
134
  if (options.request) { options.request(s, method, path, body, cb); return; }
135
135
  var opts = url.parse(BASE + path), done = false, timer;
136
136
  var data = body === undefined ? null : JSON.stringify(body);
137
- opts.method = method; opts.headers = { Authorization: auth(s), 'User-Agent': 'AniHub/0.3.1 (Samsung Tizen 3.0; Jellyfin compatible client)', 'Accept-Encoding': 'identity', Accept: 'application/json' };
137
+ opts.method = method; opts.headers = { Authorization: auth(s), 'User-Agent': 'AniHub/0.3.2 (Samsung Tizen 3.0; Jellyfin compatible client)', 'Accept-Encoding': 'identity', Accept: 'application/json' };
138
138
  if (data !== null) { opts.headers['Content-Type'] = 'application/json'; opts.headers['Content-Length'] = Buffer.byteLength(data); }
139
139
  function finish(e, value) { if (done) { return; } done = true; clearTimeout(timer); cb(e, value); }
140
140
  var req = https.request(opts, function (res) {
@@ -242,7 +242,7 @@ var ABTVAniService = (function () {
242
242
  if (t.kind === 'image') { address = '/Items/' + t.data.id + '/Images/Primary?maxWidth=480&quality=85'; }
243
243
  else if (t.kind === 'subtitle') { address = t.data.path; }
244
244
  else { address = query.part === undefined ? t.data.path : /^\d+$/.test(String(query.part)) ? t.data.parts[Number(query.part)] : ''; if (!address || !safeMedia(address)) { res.writeHead(403); res.end(); return; } }
245
- var opts = url.parse(BASE + relative(address)); opts.headers = { Authorization: auth(t.session), 'User-Agent': 'AniHub/0.3.1 (Samsung Tizen 3.0; Jellyfin compatible client)', 'Accept-Encoding': 'identity' };
245
+ var opts = url.parse(BASE + relative(address)); opts.headers = { Authorization: auth(t.session), 'User-Agent': 'AniHub/0.3.2 (Samsung Tizen 3.0; Jellyfin compatible client)', 'Accept-Encoding': 'identity' };
246
246
  upstream = https.get(opts, function (r) {
247
247
  if (r.statusCode !== 200 && r.statusCode !== 206) { r.resume(); res.writeHead(r.statusCode === 401 ? 401 : 502); res.end('Origem indisponível: HTTP ' + r.statusCode + '.'); return; }
248
248
  if (t.kind === 'subtitle') { var subtitleParts = [], subtitleSize = 0; r.on('data', function (b) { subtitleSize += b.length; if (subtitleSize > 16 * 1024 * 1024) { upstream.destroy(); } else { subtitleParts.push(b); } }); r.on('end', function () { if (res.finished || done) { return; } try { var text = toVtt(Buffer.concat(subtitleParts).toString('utf8'), t.data.codec); t.data.vtt = text; res.writeHead(200, { 'Content-Type': 'text/vtt; charset=utf-8', 'Cache-Control': 'no-store' }); res.end(text); } catch (ignore) { res.writeHead(502); res.end('Legenda WebVTT indisponível.'); } }); r.on('error', function (e) { if (!res.headersSent) { res.writeHead(502); res.end('Falha na transferência da legenda. Atualize as fontes.'); } else { res.destroy(); } }); return; }
@@ -577,7 +577,7 @@ var ABTVAniService = (function () {
577
577
  req.on('end', function () { if (tooLarge) { return; } var body; try { body = JSON.parse(aniBody); } catch (ignore) { json(res, 400, { error: 'JSON inválido.' }); return; } aniBody = ''; aniCall(body); }); return;
578
578
  }
579
579
  if (req.method !== 'GET') { json(res, 405, { error: 'Método não permitido.' }); return; }
580
- if (p.pathname === '/health') { json(res, 200, { app: 'anihub-tv', version: '0.3.1' }); return; }
580
+ if (p.pathname === '/health') { json(res, 200, { app: 'anihub-tv', version: '0.3.2' }); return; }
581
581
  if (p.pathname === '/video') { media(req, res, String(p.query.key || '')); return; }
582
582
  if (p.pathname === '/hls.m3u8') { relayHls(req, res, String(p.query.key || ''), null); return; }
583
583
  if (p.pathname === '/hls-segment') { var segmentIndex = /^\d+$/.test(String(p.query.i)) ? Number(p.query.i) : -1; relayHls(req, res, String(p.query.key || ''), segmentIndex); return; }
@@ -310,7 +310,7 @@
310
310
  req.on('end', function () { if (tooLarge) { return; } var body; try { body = JSON.parse(aniBody); } catch (ignore) { json(res, 400, { error: 'JSON inválido.' }); return; } aniBody = ''; aniCall(body); }); return;
311
311
  }
312
312
  if (req.method !== 'GET') { json(res, 405, { error: 'Método não permitido.' }); return; }
313
- if (p.pathname === '/health') { json(res, 200, { app: 'anihub-tv', version: '0.3.1' }); return; }
313
+ if (p.pathname === '/health') { json(res, 200, { app: 'anihub-tv', version: '0.3.2' }); return; }
314
314
  if (p.pathname === '/video') { media(req, res, String(p.query.key || '')); return; }
315
315
  if (p.pathname === '/hls.m3u8') { relayHls(req, res, String(p.query.key || ''), null); return; }
316
316
  if (p.pathname === '/hls-segment') { var segmentIndex = /^\d+$/.test(String(p.query.i)) ? Number(p.query.i) : -1; relayHls(req, res, String(p.query.key || ''), segmentIndex); return; }