jaynesmax commited on
Commit
ca01e4a
·
verified ·
1 Parent(s): 569952f

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