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 +4 -4
- package/docs/TESTE-NA-TV.md +7 -4
- package/index.html +3 -3
- package/js/boot.js +23 -0
- package/package.json +4 -3
- package/service/anistream.js +3 -3
- package/service/index.js +4 -4
- package/service/runtime.js +1 -1
- package/tv.html +488 -0
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
|
+
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.
|
|
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.
|
|
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**:
|
|
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
|
|
package/docs/TESTE-NA-TV.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
Instalação alternativa pelo npm: no TizenBrew, escolha Adicionar módulo → npm e digite `anihub-tv@0.3.
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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": "
|
|
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",
|
package/service/anistream.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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; }
|
package/service/runtime.js
CHANGED
|
@@ -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.
|
|
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; }
|