push
This commit is contained in:
@@ -22,13 +22,13 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
npmDeps = pkgs.fetchNpmDeps {
|
npmDeps = pkgs.fetchNpmDeps {
|
||||||
name = "streamreverse-npm-deps";
|
name = "bug-tv-npm-deps";
|
||||||
inherit src;
|
inherit src;
|
||||||
hash = "sha256-pMxVG+OHiqjz1GHWDhhNOsrhoVPzVJ5kXhiRsP0Rflg=";
|
hash = "sha256-pMxVG+OHiqjz1GHWDhhNOsrhoVPzVJ5kXhiRsP0Rflg=";
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
pkgs.stdenvNoCC.mkDerivation {
|
pkgs.stdenvNoCC.mkDerivation {
|
||||||
pname = "streamreverse";
|
pname = "bug-tv";
|
||||||
version = "0.1.0";
|
version = "0.1.0";
|
||||||
|
|
||||||
inherit src npmDeps;
|
inherit src npmDeps;
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
enable = lib.mkEnableOption "the bug.tv multi-provider stream finder API";
|
enable = lib.mkEnableOption "the bug.tv multi-provider stream finder API";
|
||||||
|
|
||||||
package = lib.mkOption {
|
package = lib.mkOption {
|
||||||
description = "The streamreverse package to run.";
|
description = "The bug-tv package to run.";
|
||||||
type = lib.types.package;
|
type = lib.types.package;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "streamreverse",
|
"name": "bug-tv",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "streamreverse",
|
"name": "bug-tv",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cheerio-without-node-native": "^0.20.2",
|
"cheerio-without-node-native": "^0.20.2",
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "streamreverse",
|
"name": "bug-tv",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Multi-provider stream finder with TMDB search and REST API",
|
"description": "Multi-provider stream finder with TMDB search and REST API",
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// OpenAPI 3.0 spec for the streamreverse REST API.
|
|
||||||
// Served at GET /api.json and rendered by Swagger UI at GET /.
|
|
||||||
|
|
||||||
const spec = {
|
const spec = {
|
||||||
openapi: '3.0.3',
|
openapi: '3.0.3',
|
||||||
|
|||||||
+3
-2
@@ -36,8 +36,9 @@
|
|||||||
|
|
||||||
<footer class="site-footer">
|
<footer class="site-footer">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<span>bug.tv</span>
|
<span>tv.bug.tools</span>
|
||||||
<span>API: <a id="apiBaseLabel" href="#" target="_blank" rel="noopener"></a></span>
|
<span>api: <a id="apiBaseLabel" href="#" target="_blank" rel="noopener"></a></span>
|
||||||
|
<span>tv.bug.tools doesn't store files, but provides links to external services.</span>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|||||||
+123
-5
@@ -1,7 +1,21 @@
|
|||||||
|
/*
|
||||||
|
API — two upstreams:
|
||||||
|
1. the bug-tv REST server (SR.config.getBase()): provider catalog,
|
||||||
|
/streams lookup, and the /proxy/media tunnel the player needs (CORS,
|
||||||
|
headers, m3u8 rewrite).
|
||||||
|
2. TMDB, called straight from the browser (it is CORS-enabled): search,
|
||||||
|
trending, details, seasons, episodes. Each returns the same shape the
|
||||||
|
server's endpoints used to, so the pages are unchanged.
|
||||||
|
|
||||||
|
Keeping catalog traffic off the server means it only does provider scraping
|
||||||
|
and stream proxying.
|
||||||
|
*/
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const TMDB_BASE = 'https://api.themoviedb.org/3';
|
||||||
|
const TMDB_IMG = 'https://image.tmdb.org/t/p/';
|
||||||
|
|
||||||
async function request(path, params) {
|
async function request(path, params) {
|
||||||
const url = new URL(SR.config.getBase() + path);
|
const url = new URL(SR.config.getBase() + path);
|
||||||
if (params) {
|
if (params) {
|
||||||
@@ -28,6 +42,38 @@
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Direct TMDB call. `key` overrides the configured key (Settings "Test").
|
||||||
|
async function tmdb(pathname, params, key) {
|
||||||
|
const url = new URL(TMDB_BASE + pathname);
|
||||||
|
url.searchParams.set('api_key', key || SR.config.getTmdbKey());
|
||||||
|
url.searchParams.set('language', 'en-US');
|
||||||
|
if (params) {
|
||||||
|
for (const [k, v] of Object.entries(params)) {
|
||||||
|
if (v === undefined || v === null || v === '') continue;
|
||||||
|
url.searchParams.set(k, String(v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let res;
|
||||||
|
try {
|
||||||
|
res = await fetch(url.toString());
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('cannot reach the TMDB API — check your connection or the TMDB API key in Settings');
|
||||||
|
}
|
||||||
|
let data = null;
|
||||||
|
try {
|
||||||
|
data = await res.json();
|
||||||
|
} catch (e) {
|
||||||
|
/* non-JSON body */
|
||||||
|
}
|
||||||
|
if (!res.ok) {
|
||||||
|
if (res.status === 401) {
|
||||||
|
throw new Error('TMDB rejected the API key — check "TMDB API key" in Settings');
|
||||||
|
}
|
||||||
|
throw new Error((data && (data.status_message || data.error)) || `TMDB error: HTTP ${res.status}`);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
// route a media/subtitle URL through the API proxy (CORS, headers, m3u8 rewrite)
|
// route a media/subtitle URL through the API proxy (CORS, headers, m3u8 rewrite)
|
||||||
function proxyUrl(target, headers) {
|
function proxyUrl(target, headers) {
|
||||||
const q = new URLSearchParams();
|
const q = new URLSearchParams();
|
||||||
@@ -36,15 +82,87 @@
|
|||||||
return SR.config.getBase() + '/proxy/media?' + q.toString();
|
return SR.config.getBase() + '/proxy/media?' + q.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Same shapes the server endpoints used to return.
|
||||||
|
function searchResult(m) {
|
||||||
|
const isTv = m.media_type === 'tv';
|
||||||
|
const date = isTv ? m.first_air_date : m.release_date;
|
||||||
|
return {
|
||||||
|
tmdb_id: m.id,
|
||||||
|
type: isTv ? 'tv' : 'movie',
|
||||||
|
name: m.title || m.name,
|
||||||
|
year: date ? String(date).slice(0, 4) : '',
|
||||||
|
poster: m.poster_path ? `${TMDB_IMG}w342${m.poster_path}` : '',
|
||||||
|
backdrop: m.backdrop_path ? `${TMDB_IMG}w1280${m.backdrop_path}` : '',
|
||||||
|
rating: m.vote_average ? Number(m.vote_average).toFixed(1) : '',
|
||||||
|
overview: m.overview || '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function fullImage(pathname) {
|
||||||
|
return pathname ? `${TMDB_IMG}original${pathname}` : '';
|
||||||
|
}
|
||||||
|
|
||||||
window.SR = window.SR || {};
|
window.SR = window.SR || {};
|
||||||
SR.api = {
|
SR.api = {
|
||||||
request,
|
request,
|
||||||
|
tmdb,
|
||||||
proxyUrl,
|
proxyUrl,
|
||||||
search: (q) => request('/search', { q }),
|
|
||||||
trending: (type, timeWindow) => request('/trending', { type, window: timeWindow }),
|
// — TMDB, straight from the browser —
|
||||||
details: (type, id) => request(`/${type}/${id}`),
|
search: (q) =>
|
||||||
seasons: (id) => request(`/tv/${id}/seasons`),
|
tmdb('/search/multi', { query: q, include_adult: 'false', page: 1 }).then((r) => {
|
||||||
episodes: (id, season) => request(`/tv/${id}/seasons/${season}`),
|
const results = (r.results || [])
|
||||||
|
.filter((m) => m.media_type === 'movie' || m.media_type === 'tv')
|
||||||
|
.slice(0, 15)
|
||||||
|
.map(searchResult);
|
||||||
|
return { query: q, count: results.length, results };
|
||||||
|
}),
|
||||||
|
trending: (type, timeWindow) => {
|
||||||
|
const t = ['movie', 'tv', 'all'].includes(type) ? type : 'all';
|
||||||
|
const w = timeWindow === 'day' ? 'day' : 'week';
|
||||||
|
return tmdb(`/trending/${t}/${w}`).then((r) => {
|
||||||
|
const results = (r.results || []).slice(0, 20).map(searchResult);
|
||||||
|
return { type: t, window: w, count: results.length, results };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
details: (type, id) =>
|
||||||
|
tmdb(`/${type}/${id}`, { append_to_response: 'images' }).then((d) => {
|
||||||
|
d.poster_url = fullImage(d.poster_path);
|
||||||
|
d.backdrop_url = fullImage(d.backdrop_path);
|
||||||
|
if (d.images) {
|
||||||
|
for (const key of Object.keys(d.images)) {
|
||||||
|
d.images[key] = (d.images[key] || []).map((im) => ({ ...im, url: fullImage(im.file_path) }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}),
|
||||||
|
seasons: (id) =>
|
||||||
|
tmdb(`/tv/${id}`).then((r) => {
|
||||||
|
const seasons = (r.seasons || [])
|
||||||
|
.filter((s) => s.season_number > 0 && (s.episode_count || 0) > 0)
|
||||||
|
.map((s) => ({
|
||||||
|
season_number: s.season_number,
|
||||||
|
name: s.name,
|
||||||
|
episode_count: s.episode_count,
|
||||||
|
air_date: s.air_date || '',
|
||||||
|
}));
|
||||||
|
return { tmdb_id: id, count: seasons.length, seasons };
|
||||||
|
}),
|
||||||
|
episodes: (id, season) =>
|
||||||
|
tmdb(`/tv/${id}/season/${season}`).then((r) => {
|
||||||
|
const episodes = (r.episodes || [])
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) => a.episode_number - b.episode_number)
|
||||||
|
.map((e) => ({
|
||||||
|
episode_number: e.episode_number,
|
||||||
|
name: e.name,
|
||||||
|
air_date: e.air_date || '',
|
||||||
|
still: e.still_path ? `${TMDB_IMG}w300${e.still_path}` : '',
|
||||||
|
}));
|
||||||
|
return { tmdb_id: id, season: parseInt(season, 10), count: episodes.length, episodes };
|
||||||
|
}),
|
||||||
|
|
||||||
|
// — bug-tv server (providers, streams, proxy) —
|
||||||
streams: ({ tmdb, type, season, episode, providers }) =>
|
streams: ({ tmdb, type, season, episode, providers }) =>
|
||||||
request('/streams', {
|
request('/streams', {
|
||||||
tmdb,
|
tmdb,
|
||||||
|
|||||||
+44
-2
@@ -9,13 +9,22 @@
|
|||||||
Providers: until the user toggles anything in Settings, the server's own
|
Providers: until the user toggles anything in Settings, the server's own
|
||||||
enabled list is used. Once customized, a {name: bool} map is stored in
|
enabled list is used. Once customized, a {name: bool} map is stored in
|
||||||
localStorage; names missing from the map default to enabled.
|
localStorage; names missing from the map default to enabled.
|
||||||
|
|
||||||
|
TMDB key: search/trending/details/images go straight to TMDB from the
|
||||||
|
browser (the server only serves providers, streams and the media proxy).
|
||||||
|
The key resolves from localStorage (Settings) or the built-in default.
|
||||||
*/
|
*/
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const DEFAULT_BASE = 'https://api-tv.bug.tools';
|
const DEFAULT_BASE = 'https://api-tv.bug.tools';
|
||||||
const STORAGE_KEY = 'streamreverse.apiBase.v1';
|
const STORAGE_KEY = 'bug-tv.apiBase.v1';
|
||||||
const PROVIDER_KEY = 'streamreverse.providers.v1';
|
const PROVIDER_KEY = 'bug-tv.providers.v1';
|
||||||
|
|
||||||
|
// Search/details/images go straight to TMDB from the browser; this key
|
||||||
|
// only ever reaches api.themoviedb.org.
|
||||||
|
const DEFAULT_TMDB_KEY = 'ac014b130a8e6344c91dff4e68b18d47';
|
||||||
|
const TMDB_KEY_STORAGE_KEY = 'bug-tv.tmdbKey.v1';
|
||||||
|
|
||||||
function normalize(value) {
|
function normalize(value) {
|
||||||
let v = String(value || '').trim();
|
let v = String(value || '').trim();
|
||||||
@@ -54,6 +63,34 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTmdbKey() {
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem(TMDB_KEY_STORAGE_KEY);
|
||||||
|
if (stored) return stored.trim();
|
||||||
|
} catch (e) {
|
||||||
|
/* private mode etc. */
|
||||||
|
}
|
||||||
|
return DEFAULT_TMDB_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTmdbKey(value) {
|
||||||
|
const v = String(value || '').trim();
|
||||||
|
try {
|
||||||
|
localStorage.setItem(TMDB_KEY_STORAGE_KEY, v);
|
||||||
|
} catch (e) {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearTmdbKey() {
|
||||||
|
try {
|
||||||
|
localStorage.removeItem(TMDB_KEY_STORAGE_KEY);
|
||||||
|
} catch (e) {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// null → user has not customized; caller should fall back to server defaults
|
// null → user has not customized; caller should fall back to server defaults
|
||||||
function getProviders() {
|
function getProviders() {
|
||||||
try {
|
try {
|
||||||
@@ -128,9 +165,14 @@
|
|||||||
DEFAULT_BASE,
|
DEFAULT_BASE,
|
||||||
STORAGE_KEY,
|
STORAGE_KEY,
|
||||||
PROVIDER_KEY,
|
PROVIDER_KEY,
|
||||||
|
DEFAULT_TMDB_KEY,
|
||||||
|
TMDB_KEY_STORAGE_KEY,
|
||||||
getBase,
|
getBase,
|
||||||
setBase,
|
setBase,
|
||||||
clearBase,
|
clearBase,
|
||||||
|
getTmdbKey,
|
||||||
|
setTmdbKey,
|
||||||
|
clearTmdbKey,
|
||||||
getProviders,
|
getProviders,
|
||||||
setProviders,
|
setProviders,
|
||||||
clearProviders,
|
clearProviders,
|
||||||
|
|||||||
+33
-1
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
SETTINGS — source provider toggles, API server base URL, local data.
|
SETTINGS — source provider toggles, API server base URL, TMDB key, local data.
|
||||||
*/
|
*/
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
@@ -10,6 +10,8 @@
|
|||||||
const providersStatus = document.getElementById('providersStatus');
|
const providersStatus = document.getElementById('providersStatus');
|
||||||
const apiBaseInput = document.getElementById('apiBaseInput');
|
const apiBaseInput = document.getElementById('apiBaseInput');
|
||||||
const connStatus = document.getElementById('connStatus');
|
const connStatus = document.getElementById('connStatus');
|
||||||
|
const tmdbKeyInput = document.getElementById('tmdbKeyInput');
|
||||||
|
const tmdbStatus = document.getElementById('tmdbStatus');
|
||||||
const dataStatus = document.getElementById('dataStatus');
|
const dataStatus = document.getElementById('dataStatus');
|
||||||
|
|
||||||
/* ---- nav --------------------------------------------------------------------- */
|
/* ---- nav --------------------------------------------------------------------- */
|
||||||
@@ -139,6 +141,36 @@
|
|||||||
await testConnection();
|
await testConnection();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* ---- TMDB key ------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
tmdbKeyInput.value = SR.config.getTmdbKey();
|
||||||
|
|
||||||
|
async function testTmdbKey(key) {
|
||||||
|
setStatus(tmdbStatus, 'Testing TMDB key…');
|
||||||
|
try {
|
||||||
|
await SR.api.tmdb('/search/movie', { query: 'The Matrix', page: 1 }, key);
|
||||||
|
setStatus(tmdbStatus, 'TMDB key works — search, details and images load directly from TMDB.', 'ok');
|
||||||
|
} catch (e) {
|
||||||
|
setStatus(tmdbStatus, e.message, 'err');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('tmdbKeySave').addEventListener('click', async () => {
|
||||||
|
SR.config.setTmdbKey(tmdbKeyInput.value);
|
||||||
|
tmdbKeyInput.value = SR.config.getTmdbKey();
|
||||||
|
await testTmdbKey(SR.config.getTmdbKey());
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('tmdbKeyTest').addEventListener('click', async () => {
|
||||||
|
await testTmdbKey(tmdbKeyInput.value.trim());
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('tmdbKeyReset').addEventListener('click', async () => {
|
||||||
|
SR.config.clearTmdbKey();
|
||||||
|
tmdbKeyInput.value = SR.config.getTmdbKey();
|
||||||
|
await testTmdbKey(SR.config.getTmdbKey());
|
||||||
|
});
|
||||||
|
|
||||||
/* ---- data ---------------------------------------------------------------------------- */
|
/* ---- data ---------------------------------------------------------------------------- */
|
||||||
|
|
||||||
document.getElementById('clearHistory').addEventListener('click', () => {
|
document.getElementById('clearHistory').addEventListener('click', () => {
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@
|
|||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const KEY = 'streamreverse.resume.v1';
|
const KEY = 'bug-tv.resume.v1';
|
||||||
const MAX_RECORDS = 200;
|
const MAX_RECORDS = 200;
|
||||||
|
|
||||||
function readAll() {
|
function readAll() {
|
||||||
|
|||||||
+1
-1
@@ -714,7 +714,7 @@
|
|||||||
b.type = 'button';
|
b.type = 'button';
|
||||||
b.role = 'tab';
|
b.role = 'tab';
|
||||||
b.className = s.season_number === state.season ? 'chip chip--active' : 'chip';
|
b.className = s.season_number === state.season ? 'chip chip--active' : 'chip';
|
||||||
b.textContent = `S${s.season_number}`;
|
b.textContent = `Season ${s.season_number}`;
|
||||||
b.title = `${s.name || `Season ${s.season_number}`} (${s.episode_count || 0})`;
|
b.title = `${s.name || `Season ${s.season_number}`} (${s.episode_count || 0})`;
|
||||||
b.addEventListener('click', () => {
|
b.addEventListener('click', () => {
|
||||||
if (s.season_number !== state.season) loadEpisodes(s.season_number, null);
|
if (s.season_number !== state.season) loadEpisodes(s.season_number, null);
|
||||||
|
|||||||
+3
-2
@@ -44,8 +44,9 @@
|
|||||||
|
|
||||||
<footer class="site-footer">
|
<footer class="site-footer">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<span>bug.tv</span>
|
<span>tv.bug.tools</span>
|
||||||
<span>API: <a id="apiBaseLabel" href="#" target="_blank" rel="noopener"></a></span>
|
<span>api: <a id="apiBaseLabel" href="#" target="_blank" rel="noopener"></a></span>
|
||||||
|
<span>tv.bug.tools doesn't store files, but provides links to external services.</span>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|||||||
+13
-3
@@ -50,7 +50,7 @@
|
|||||||
|
|
||||||
<section class="settings__card" id="server">
|
<section class="settings__card" id="server">
|
||||||
<h2>Server</h2>
|
<h2>Server</h2>
|
||||||
<p class="settings__hint">The REST API that bug.tv queries. Change this only if you run your own instance.</p>
|
<p class="settings__hint">The REST API that bug.tv uses for providers, stream lookups and the media proxy. Change this only if you run your own instance.</p>
|
||||||
<label class="field-label" for="apiBaseInput">API base URL</label>
|
<label class="field-label" for="apiBaseInput">API base URL</label>
|
||||||
<input class="field" id="apiBaseInput" type="text" spellcheck="false" autocomplete="off" placeholder="https://api-tv.bug.tools">
|
<input class="field" id="apiBaseInput" type="text" spellcheck="false" autocomplete="off" placeholder="https://api-tv.bug.tools">
|
||||||
<div class="settings__actions">
|
<div class="settings__actions">
|
||||||
@@ -58,6 +58,15 @@
|
|||||||
<button class="btn btn--ghost" id="apiBaseReset" type="button">Reset to default</button>
|
<button class="btn btn--ghost" id="apiBaseReset" type="button">Reset to default</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="settings__status" id="connStatus"></p>
|
<p class="settings__status" id="connStatus"></p>
|
||||||
|
<p class="settings__hint">Search, details and images go straight to TMDB from your browser, using the key below — your server is not involved.</p>
|
||||||
|
<label class="field-label" for="tmdbKeyInput">TMDB API key</label>
|
||||||
|
<input class="field" id="tmdbKeyInput" type="text" spellcheck="false" autocomplete="off" placeholder="32-character TMDB API key">
|
||||||
|
<div class="settings__actions">
|
||||||
|
<button class="btn btn--primary" id="tmdbKeySave" type="button">Save</button>
|
||||||
|
<button class="btn btn--ghost" id="tmdbKeyTest" type="button">Test</button>
|
||||||
|
<button class="btn btn--ghost" id="tmdbKeyReset" type="button">Reset to default</button>
|
||||||
|
</div>
|
||||||
|
<p class="settings__status" id="tmdbStatus"></p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="settings__card" id="data">
|
<section class="settings__card" id="data">
|
||||||
@@ -75,8 +84,9 @@
|
|||||||
|
|
||||||
<footer class="site-footer">
|
<footer class="site-footer">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<span>bug.tv</span>
|
<span>tv.bug.tools</span>
|
||||||
<span>API: <a id="apiBaseLabel" href="#" target="_blank" rel="noopener"></a></span>
|
<span>api: <a id="apiBaseLabel" href="#" target="_blank" rel="noopener"></a></span>
|
||||||
|
<span>tv.bug.tools doesn't store files, but provides links to external services.</span>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|||||||
+4
-3
@@ -39,7 +39,7 @@
|
|||||||
<p id="heroOverview" class="watch-hero__overview"></p>
|
<p id="heroOverview" class="watch-hero__overview"></p>
|
||||||
<div class="watch-hero__actions">
|
<div class="watch-hero__actions">
|
||||||
<button id="playButton" class="btn btn--primary" type="button">
|
<button id="playButton" class="btn btn--primary" type="button">
|
||||||
<span id="playLabel">Play</span>
|
<span id="playLabel">Play</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -116,8 +116,9 @@
|
|||||||
|
|
||||||
<footer class="site-footer">
|
<footer class="site-footer">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<span>bug.tv</span>
|
<span>tv.bug.tools</span>
|
||||||
<span>API: <a id="apiBaseLabel" href="#" target="_blank" rel="noopener"></a></span>
|
<span>api: <a id="apiBaseLabel" href="#" target="_blank" rel="noopener"></a></span>
|
||||||
|
<span>tv.bug.tools doesn't store files, but provides links to external services.</span>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user