jaynesmax commited on
Commit
e7950a3
·
verified ·
1 Parent(s): f251913

Upload index.html

Browse files
Files changed (1) hide show
  1. index.html +99 -44
index.html CHANGED
@@ -1226,6 +1226,13 @@
1226
  /* Nafasi ya kusogeza (scroll) ili kipengele chenye focus kisifichwe na topbar / nav */
1227
  .poster,.cw-card,.tab-chip,.settings-row,.dq-item,.tr-ep,.cast-item{scroll-margin:110px 3vw 110px 3vw;}
1228
  </style>
 
 
 
 
 
 
 
1229
  </head>
1230
  <body>
1231
  <div id="gate">
@@ -1498,34 +1505,24 @@ const PIXTVMAX_KEY = SUPABASE_ANON_KEY;
1498
  const TRAILER_BASE = JM_CONFIG.CONTENT_URL + '/functions/v1/movie-trailer';
1499
  const TRAILER_KEY = SUPABASE_ANON_KEY;
1500
 
1501
- // movie-zilizotafsiriwa — mradi TOFAUTI (DJ movies/series), function ya umma pekee (GET)
1502
- const TRANSLATED_BASE = JM_CONFIG.TRANSLATED_URL + '/functions/v1/' + JM_CONFIG.TRANSLATED_FN;
1503
- const TRANSLATED_KEY = JM_CONFIG.TRANSLATED_KEY;
1504
- // movie-zilizotafsriwa2 — filamu zenye MP4 (draft/public kutoka admin); project ile ile ya CONTENT_URL.
1505
- // Id zake zinaanza na 'n2-' ili zisigongane na za chanzo cha zamani.
1506
- const TRANSLATED2_BASE = JM_CONFIG.CONTENT_URL + '/functions/v1/movie-zilizotafsriwa2';
1507
- const TR2_PREFIX = 'n2-';
1508
- const TR_DJ_NOISE = /\b(movies?|filamu|zote|zilizotafsiriwa|zilizotafsiri|imetafsiriwa|iliyotafsiriwa|tafsiriwa|kiswahili|swahili|kwa|mpya|kali|translated|new|all|series|full)\b/gi;
1509
  function normDj(raw) { // jina safi la DJ (bila maneno ya ziada na bila tofauti za herufi kubwa/ndogo)
1510
  const s = String(raw || '').replace(/\(.*?\)|\[.*?\]/g, ' ').replace(TR_DJ_NOISE, ' ').replace(/[|_\-\u2013\u2014:,.]+/g, ' ').replace(/\s+/g, ' ').trim();
1511
  if (!s || /^dj$/i.test(s)) return '';
1512
- return s.split(' ').map(w => /^dj$/i.test(w) ? 'DJ' : w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' ');
 
1513
  }
 
 
1514
  // Model iliyofunzwa (train.py -> HF Model). Ikikosekana, app inafanya kazi kama kawaida (Trending = mpya zaidi).
 
1515
  const TR_MODEL_URL = 'https://huggingface.co/jaynesmaxtv/translated-recommender/resolve/main/model.json';
1516
  let trModelP = null;
1517
  function loadTrModel() {
1518
  if (!trModelP) trModelP = fetch(TR_MODEL_URL, { cache: 'no-cache' }).then(r => r.ok ? r.json() : null).catch(() => null);
1519
  return trModelP;
1520
  }
1521
- function logTrPlay(id) { // rekodi uchezaji bila kuzuia player; kitambulisho cha kifaa ni cha nasibu (hakuna jina/email)
1522
- if (!id) return;
1523
- let dev = '';
1524
- try { dev = localStorage.getItem('jm_dev') || ''; if (!dev) { dev = (crypto.randomUUID && crypto.randomUUID()) || (Date.now() + '-' + Math.random().toString(36).slice(2)); localStorage.setItem('jm_dev', dev); } } catch (_) {}
1525
- fetch(TRANSLATED2_BASE + '?action=play', { method: 'POST', keepalive: true,
1526
- headers: { apikey: SUPABASE_ANON_KEY, 'Content-Type': 'application/json' },
1527
- body: JSON.stringify({ movie_id: String(id), device_id: dev }) }).catch(() => {});
1528
- }
1529
  // Rails za mlalo: Trending Now (Top 10, kwa model au mpya zaidi) -> New Releases -> kila DJ (DJ Mack kwanza) -> Series -> More to Watch
1530
  function buildTranslatedSections(items, model) {
1531
  const pop = (model && model.popularity) || {}, sections = [], covered = new Set();
@@ -1548,13 +1545,11 @@ function buildTranslatedSections(items, model) {
1548
  if (more.length) sections.push({ title: t('tr_more'), items: more.slice(0, 40) });
1549
  return sections;
1550
  }
1551
- async function fetchTranslatedMovie(id) { // -> { ok, error?, movie } kutoka chanzo sahihi
1552
- const isNew = String(id).startsWith(TR2_PREFIX);
1553
- const url = isNew ? TRANSLATED2_BASE + '?id=' + encodeURIComponent(String(id).slice(TR2_PREFIX.length))
1554
- : TRANSLATED_BASE + '?id=' + encodeURIComponent(id);
1555
- const r = await fetch(url, { headers: { apikey: isNew ? SUPABASE_ANON_KEY : TRANSLATED_KEY } });
1556
- const d = await r.json();
1557
- return isNew ? { ok: r.ok && !d.error, error: d.error, movie: d } : d;
1558
  }
1559
  const PIXTVMAX_CATEGORY_SW = {
1560
  'Sports': 'cat_sports', 'Movie Channels': 'cat_movies', 'News': 'cat_news',
@@ -2952,6 +2947,13 @@ const I18N_EXTRA10 = {
2952
  fr: { jtv_next: 'Ensuite', jtv_schedule: 'Programme', jtv_down: 'JaynesTV est indisponible', tr_trending: 'Tendances actuelles', tr_new: 'Nouveautés', tr_more: 'À découvrir' }
2953
  };
2954
  Object.keys(I18N_EXTRA10).forEach(l => { I18N[l] = Object.assign(I18N[l] || {}, I18N_EXTRA10[l]); });
 
 
 
 
 
 
 
2955
  function T(k, v) { return t(k, v); }
2956
  // Maandishi yaliyoandikwa kwenye HTML kupitia data-i18n / data-i18n-ph / data-i18n-title
2957
  function applyI18nAttrs() {
@@ -3835,25 +3837,78 @@ async function fetchTranslatedListRaw() {
3835
  id: idPrefix + m.id, kind: 'translated', title: m.title,
3836
  poster: m.thumb_url || m.poster_url || null, imgFallbackOn: true,
3837
  subtitle: normDj(m.dj) || (m.category === 'series' ? 'Series' : 'Movie'), isSeries: m.category === 'series',
 
3838
  });
3839
- const oldP = (async () => { // chanzo cha zamani
3840
- const r = await fetch(TRANSLATED_BASE + '?limit=100', { headers: { apikey: TRANSLATED_KEY } });
3841
- if (!r.ok) throw new Error(T('err_generic') + ' ' + r.status);
3842
- const d = await r.json();
3843
- if (!d.ok) throw new Error(d.error || 'Seva imekataa ombi.');
3844
- return (d.movies || []).map(m => card(m, ''));
3845
- })();
3846
- const newP = (async () => { // movie-zilizotafsriwa2: public tu (seva inachuja)
3847
- const r = await fetch(TRANSLATED2_BASE, { headers: { apikey: SUPABASE_ANON_KEY } });
3848
- if (!r.ok) throw new Error(T('err_generic') + ' ' + r.status);
3849
- const d = await r.json();
3850
- return (Array.isArray(d) ? d : []).map(m => card(m, TR2_PREFIX));
3851
- })();
3852
- const [a, b] = await Promise.allSettled([oldP, newP]);
3853
- if (a.status === 'rejected' && b.status === 'rejected') throw a.reason;
3854
- const fresh = b.status === 'fulfilled' ? b.value : [], older = a.status === 'fulfilled' ? a.value : [];
3855
- const seen = new Set(fresh.map(x => String(x.title || '').trim().toLowerCase()));
3856
- return fresh.concat(older.filter(x => !seen.has(String(x.title || '').trim().toLowerCase())));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3857
  }
3858
 
3859
  async function loadTranslated() {
@@ -3861,7 +3916,8 @@ async function loadTranslated() {
3861
  renderSkeletonRails(2);
3862
  try {
3863
  const [items, model] = await Promise.all([fetchTranslatedList(), loadTrModel()]);
3864
- renderRails(buildTranslatedSections(items, model), false);
 
3865
  } catch (e) {
3866
  railsEl.innerHTML = '';
3867
  railsEl.appendChild(stateBox(t('err_failed', { e: esc(e.message || t('err_generic')) }), true, loadTranslated));
@@ -4093,7 +4149,6 @@ function playTranslatedUrl(url, title, playlist, meta) {
4093
  v.addEventListener('ended', () => { const c = window.__pcPlaylist; if (c && typeof c.next === 'function') c.next(); });
4094
  // Tangazo la video kwanza (likishindikana/likiruka, filamu inaanza mara moja)
4095
  v.src = url;
4096
- try { logTrPlay(currentTranslatedDetail && currentTranslatedDetail.id); } catch (_) {}
4097
  // Route ya player: #/translated/ID/play?se=..&ep=..
4098
  try {
4099
  const id = currentTranslatedDetail && currentTranslatedDetail.id;
 
1226
  /* Nafasi ya kusogeza (scroll) ili kipengele chenye focus kisifichwe na topbar / nav */
1227
  .poster,.cw-card,.tab-chip,.settings-row,.dq-item,.tr-ep,.cast-item{scroll-margin:110px 3vw 110px 3vw;}
1228
  </style>
1229
+ <style>
1230
+ .tr-filter{margin:0 0 10px;}
1231
+ .tr-filter .tabs{padding:10px 16px 4px;}
1232
+ .tr-filter .tr-dj-row{padding-top:4px;}
1233
+ .tr-filter .tr-dj-row .tab-chip{font-size:12px; padding:6px 12px;}
1234
+ .tr-filter-note{padding:4px 16px 6px; font-size:12px; color:var(--muted);}
1235
+ </style>
1236
  </head>
1237
  <body>
1238
  <div id="gate">
 
1505
  const TRAILER_BASE = JM_CONFIG.CONTENT_URL + '/functions/v1/movie-trailer';
1506
  const TRAILER_KEY = SUPABASE_ANON_KEY;
1507
 
1508
+ // Zilizotafsiriwa: chanzo kimoja (movie-translated-all) — n2-, ab-, abm- ids
1509
+ const TR_DJ_NOISE = /\b(movies?|filamu|zote|zilizotafsiriwa|zilizotafsiri|imetafsiriwa|iliyotafsiriwa|tafsiriwa|kiswahili|swahili|kwa|mpya|kali|translated|new|all|series|full|single|singo|action|cartoon|katuni|season|seasons|(?:19|20)\d{2})\b/gi;
 
 
 
 
 
 
1510
  function normDj(raw) { // jina safi la DJ (bila maneno ya ziada na bila tofauti za herufi kubwa/ndogo)
1511
  const s = String(raw || '').replace(/\(.*?\)|\[.*?\]/g, ' ').replace(TR_DJ_NOISE, ' ').replace(/[|_\-\u2013\u2014:,.]+/g, ' ').replace(/\s+/g, ' ').trim();
1512
  if (!s || /^dj$/i.test(s)) return '';
1513
+ const name = s.split(' ').map(w => /^dj$/i.test(w) ? 'DJ' : w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' ');
1514
+ return TR_DJ_ALIAS[name.toLowerCase()] || name;
1515
  }
1516
+ // Majina tofauti ya DJ mmoja (andika kwa herufi ndogo upande wa kushoto)
1517
+ const TR_DJ_ALIAS = { 'dj blaki': 'DJ Black', 'blaki': 'DJ Black', 'black': 'DJ Black' };
1518
  // Model iliyofunzwa (train.py -> HF Model). Ikikosekana, app inafanya kazi kama kawaida (Trending = mpya zaidi).
1519
+ const TRANSLATED_ALL = JM_CONFIG.CONTENT_URL + '/functions/v1/movie-translated-all';
1520
  const TR_MODEL_URL = 'https://huggingface.co/jaynesmaxtv/translated-recommender/resolve/main/model.json';
1521
  let trModelP = null;
1522
  function loadTrModel() {
1523
  if (!trModelP) trModelP = fetch(TR_MODEL_URL, { cache: 'no-cache' }).then(r => r.ok ? r.json() : null).catch(() => null);
1524
  return trModelP;
1525
  }
 
 
 
 
 
 
 
 
1526
  // Rails za mlalo: Trending Now (Top 10, kwa model au mpya zaidi) -> New Releases -> kila DJ (DJ Mack kwanza) -> Series -> More to Watch
1527
  function buildTranslatedSections(items, model) {
1528
  const pop = (model && model.popularity) || {}, sections = [], covered = new Set();
 
1545
  if (more.length) sections.push({ title: t('tr_more'), items: more.slice(0, 40) });
1546
  return sections;
1547
  }
1548
+ async function fetchTranslatedMovie(id) { // -> { ok, error?, movie } kutoka movie-translated-all
1549
+ const r = await fetch(TRANSLATED_ALL + '?id=' + encodeURIComponent(id), { headers: { apikey: SUPABASE_ANON_KEY } });
1550
+ const d = await r.json().catch(() => ({}));
1551
+ if (!r.ok || !d.ok) return { ok: false, error: d.error || ('HTTP ' + r.status) };
1552
+ return d;
 
 
1553
  }
1554
  const PIXTVMAX_CATEGORY_SW = {
1555
  'Sports': 'cat_sports', 'Movie Channels': 'cat_movies', 'News': 'cat_news',
 
2947
  fr: { jtv_next: 'Ensuite', jtv_schedule: 'Programme', jtv_down: 'JaynesTV est indisponible', tr_trending: 'Tendances actuelles', tr_new: 'Nouveautés', tr_more: 'À découvrir' }
2948
  };
2949
  Object.keys(I18N_EXTRA10).forEach(l => { I18N[l] = Object.assign(I18N[l] || {}, I18N_EXTRA10[l]); });
2950
+ const I18N_EXTRA_TRF = {
2951
+ sw: { trf_all: 'Zote', trf_new: 'Mpya', trf_trend_movie: 'Movie Zinazovuma', trf_trend_series: 'Series Zinazovuma', trf_movie: 'Movie', trf_series: 'Series', trf_dj: 'Kwa DJ', trf_no_trend: 'Bado hakuna takwimu za kutosha za zinazovuma, tunaonyesha mpya zaidi kwanza.' },
2952
+ en: { trf_all: 'All', trf_new: 'New Releases', trf_trend_movie: 'Trending Movies', trf_trend_series: 'Trending Series', trf_movie: 'Movies', trf_series: 'Series', trf_dj: 'By DJ', trf_no_trend: 'Not enough viewing data yet, showing newest first.' },
2953
+ ar: { trf_all: 'الكل', trf_new: 'الجديد', trf_trend_movie: 'أفلام رائجة', trf_trend_series: 'مسلسلات رائجة', trf_movie: 'أفلام', trf_series: 'مسلسلات', trf_dj: 'حسب DJ', trf_no_trend: 'لا توجد بيانات مشاهدة كافية بعد، نعرض الأحدث أولاً.' },
2954
+ fr: { trf_all: 'Tout', trf_new: 'Nouveautés', trf_trend_movie: 'Films tendance', trf_trend_series: 'Séries tendance', trf_movie: 'Films', trf_series: 'Séries', trf_dj: 'Par DJ', trf_no_trend: "Pas encore assez de données, les plus récents d'abord." }
2955
+ };
2956
+ Object.keys(I18N_EXTRA_TRF).forEach(l => { I18N[l] = Object.assign(I18N[l] || {}, I18N_EXTRA_TRF[l]); });
2957
  function T(k, v) { return t(k, v); }
2958
  // Maandishi yaliyoandikwa kwenye HTML kupitia data-i18n / data-i18n-ph / data-i18n-title
2959
  function applyI18nAttrs() {
 
3837
  id: idPrefix + m.id, kind: 'translated', title: m.title,
3838
  poster: m.thumb_url || m.poster_url || null, imgFallbackOn: true,
3839
  subtitle: normDj(m.dj) || (m.category === 'series' ? 'Series' : 'Movie'), isSeries: m.category === 'series',
3840
+ added: m.created_at || m.updated_at || '',
3841
  });
3842
+ const r = await fetch(TRANSLATED_ALL, { headers: { apikey: SUPABASE_ANON_KEY } });
3843
+ if (!r.ok) throw new Error(T('err_generic') + ' ' + r.status);
3844
+ const d = await r.json();
3845
+ if (!d.ok) throw new Error(d.error || 'Imekataa ombi.');
3846
+ return (d.movies || []).map(m => card(m, ''));
3847
+ }
3848
+
3849
+ // ── KUPANGA ZILIZOTAFSIRIWA: Zote / Mpya / Movie Zinazovuma / Series Zinazovuma / Movie / Series / Kwa DJ ──
3850
+ let trCache = null; // { items, model } -- kubadilisha kichujio hakupakui upya
3851
+ let trFilter = 'all', trDj = '';
3852
+ try { trFilter = sessionStorage.getItem('jm_tr_filter') || 'all'; trDj = sessionStorage.getItem('jm_tr_dj') || ''; } catch (_) {}
3853
+ const TR_FILTERS = ['all', 'new', 'trend_movie', 'trend_series', 'movie', 'series', 'dj'];
3854
+ function trDjGroups(items) { // DJ Mack kwanza, kisha DJ mwenye kazi nyingi zaidi
3855
+ const g = {};
3856
+ items.forEach(it => {
3857
+ const dj = String(it.subtitle || '').trim();
3858
+ if (!dj || dj === 'Series' || dj === 'Movie') return;
3859
+ (g[dj.toLowerCase()] = g[dj.toLowerCase()] || { name: dj, list: [] }).list.push(it);
3860
+ });
3861
+ return Object.values(g).sort((a, b) => (/mack/i.test(b.name) - /mack/i.test(a.name)) || b.list.length - a.list.length || a.name.localeCompare(b.name));
3862
+ }
3863
+ function trByNewest(list) { // mpya -> zamani kwa tarehe ya kuongezwa; zisizo na tarehe zinabaki kwa mpangilio wa seva
3864
+ return list.map((it, i) => [it, i])
3865
+ .sort((a, b) => (Date.parse(b[0].added) || 0) - (Date.parse(a[0].added) || 0) || a[1] - b[1]).map(x => x[0]);
3866
+ }
3867
+ function trTrending(list, model) { // kwa uchezaji halisi (model); zikipungua, zinaongezwa mpya zaidi bila namba
3868
+ const pop = (model && model.popularity) || {};
3869
+ const hot = list.filter(it => pop[it.id] > 0).sort((a, b) => pop[b.id] - pop[a.id]);
3870
+ const ranked = hot.slice(0, 30).map((it, i) => ({ ...it, rank: i < 10 ? i + 1 : undefined }));
3871
+ if (ranked.length < 10) {
3872
+ const have = new Set(ranked.map(it => it.id));
3873
+ trByNewest(list).forEach(it => { if (ranked.length < 30 && !have.has(it.id)) ranked.push(it); });
3874
+ }
3875
+ return { items: ranked, fallback: hot.length === 0 };
3876
+ }
3877
+ function renderTranslatedView() {
3878
+ if (!trCache) return;
3879
+ const { items, model } = trCache;
3880
+ const movies = items.filter(it => !it.isSeries), series = items.filter(it => it.isSeries);
3881
+ const groups = trDjGroups(items);
3882
+ if (!TR_FILTERS.includes(trFilter)) trFilter = 'all';
3883
+ if (trFilter === 'dj' && !groups.some(g => g.name === trDj)) trDj = groups[0] ? groups[0].name : '';
3884
+ let sections, grid = true, note = '';
3885
+ if (trFilter === 'new') sections = [{ title: t('tr_new'), items: trByNewest(items) }];
3886
+ else if (trFilter === 'trend_movie' || trFilter === 'trend_series') {
3887
+ const r = trTrending(trFilter === 'trend_movie' ? movies : series, model);
3888
+ if (r.fallback) note = t('trf_no_trend');
3889
+ sections = [{ title: t('trf_' + trFilter), items: r.items }];
3890
+ }
3891
+ else if (trFilter === 'movie') sections = [{ title: t('trf_movie'), items: trByNewest(movies) }];
3892
+ else if (trFilter === 'series') sections = [{ title: t('trf_series'), items: trByNewest(series) }];
3893
+ else if (trFilter === 'dj') {
3894
+ const g = groups.find(x => x.name === trDj);
3895
+ sections = g ? [{ title: g.name, items: trByNewest(g.list).map(it => ({ ...it, subtitle: it.isSeries ? 'Series' : '' })) }] : [];
3896
+ }
3897
+ else { sections = buildTranslatedSections(items, model); grid = false; }
3898
+ renderRails(sections, grid);
3899
+ const bar = document.createElement('div');
3900
+ bar.className = 'tr-filter';
3901
+ bar.innerHTML = `<div class="tabs">${TR_FILTERS.map(f => `<div class="tab-chip${f === trFilter ? ' active' : ''}" data-trf="${f}">${esc(t('trf_' + f))}</div>`).join('')}</div>` +
3902
+ (trFilter === 'dj' && groups.length ? `<div class="tabs tr-dj-row">${groups.map(g => `<div class="tab-chip${g.name === trDj ? ' active' : ''}" data-trdj="${esc(g.name)}">${esc(g.name)} · ${g.list.length}</div>`).join('')}</div>` : '') +
3903
+ (note ? `<div class="tr-filter-note">${esc(note)}</div>` : '');
3904
+ bar.onclick = (e) => {
3905
+ const c = e.target.closest('.tab-chip'); if (!c) return;
3906
+ if (c.dataset.trf) trFilter = c.dataset.trf; else if (c.dataset.trdj != null) trDj = c.dataset.trdj; else return;
3907
+ try { sessionStorage.setItem('jm_tr_filter', trFilter); sessionStorage.setItem('jm_tr_dj', trDj); } catch (_) {}
3908
+ renderTranslatedView();
3909
+ };
3910
+ railsEl.insertBefore(bar, railsEl.firstChild);
3911
+ bar.querySelectorAll('.tab-chip.active').forEach(c => { const row = c.parentElement; row.scrollLeft = c.offsetLeft - (row.clientWidth - c.offsetWidth) / 2; });
3912
  }
3913
 
3914
  async function loadTranslated() {
 
3916
  renderSkeletonRails(2);
3917
  try {
3918
  const [items, model] = await Promise.all([fetchTranslatedList(), loadTrModel()]);
3919
+ trCache = { items, model };
3920
+ renderTranslatedView();
3921
  } catch (e) {
3922
  railsEl.innerHTML = '';
3923
  railsEl.appendChild(stateBox(t('err_failed', { e: esc(e.message || t('err_generic')) }), true, loadTranslated));
 
4149
  v.addEventListener('ended', () => { const c = window.__pcPlaylist; if (c && typeof c.next === 'function') c.next(); });
4150
  // Tangazo la video kwanza (likishindikana/likiruka, filamu inaanza mara moja)
4151
  v.src = url;
 
4152
  // Route ya player: #/translated/ID/play?se=..&ep=..
4153
  try {
4154
  const id = currentTranslatedDetail && currentTranslatedDetail.id;