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

Upload 2 files

Browse files
Files changed (2) hide show
  1. config.js +2 -1
  2. index.html +54 -102
config.js CHANGED
@@ -21,7 +21,8 @@ window.JM_CONFIG = Object.freeze({
21
  TRANSLATED_FN: 'movie-zilizotafsriwa',
22
 
23
  // Michezo (fawa-live) — mradi mpya (function imewekwa bila JWT, key haihitajiki lakini inatumwa tu)
24
- FAWA_URL: 'https://kqqdsumvvbuvyoneanku.supabase.co',
 
25
  FAWA_KEY: 'sb_publishable_0XQ9PQ0CRJnC0A_Tz18oGg_fSONkxB3',
26
 
27
  // Web Push (VAPID) — funguo YA UMMA tu. Private key iwe Supabase secrets pekee.
 
21
  TRANSLATED_FN: 'movie-zilizotafsriwa',
22
 
23
  // Michezo (fawa-live) — mradi mpya (function imewekwa bila JWT, key haihitajiki lakini inatumwa tu)
24
+ FAWA_URL: 'https://fawa-live.jaynes-max.workers.dev',
25
+ FAWA_FALLBACK_URL: 'https://kqqdsumvvbuvyoneanku.supabase.co', // akiba: Supabase fawa-live (hutumika Cloudflare ikishindwa)
26
  FAWA_KEY: 'sb_publishable_0XQ9PQ0CRJnC0A_Tz18oGg_fSONkxB3',
27
 
28
  // Web Push (VAPID) — funguo YA UMMA tu. Private key iwe Supabase secrets pekee.
index.html CHANGED
@@ -1226,13 +1226,6 @@
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">
@@ -1486,6 +1479,8 @@ const SEARCH_FN_URL = JM_CONFIG.CONTENT_URL + '/functions/v1/search';
1486
  // fawa-live (mechi/michezo moja kwa moja) — function tofauti, key ile ile
1487
  const FAWA_BASE = (JM_CONFIG.FAWA_URL || JM_CONFIG.CONTENT_URL) + '/functions/v1/fawa-live';
1488
  const FAWA_KEY = JM_CONFIG.FAWA_KEY || SUPABASE_ANON_KEY;
 
 
1489
 
1490
  // jaynes-max-tv (LIVE TV — channel za kawaida, DASH/HLS + ClearKey) — function yetu wenyewe
1491
  const JAYNES_MAX_TV_BASE = JM_CONFIG.CONTENT_URL + '/functions/v1/jaynes-max-tv';
@@ -1503,24 +1498,34 @@ const PIXTVMAX_KEY = SUPABASE_ANON_KEY;
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,11 +1548,13 @@ function buildTranslatedSections(items, model) {
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,13 +2952,6 @@ const I18N_EXTRA10 = {
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,78 +3835,25 @@ 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
- 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,8 +3861,7 @@ 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,6 +4093,7 @@ function playTranslatedUrl(url, title, playlist, meta) {
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;
@@ -6396,10 +6343,15 @@ async function playHlsUrl(url, opts = {}) {
6396
 
6397
  async function openFawaMatch(item) {
6398
  openPlayerShell(item.title);
6399
- const url = FAWA_BASE + '/proxy?slug=' + encodeURIComponent(item.id);
6400
- const opts = { authHeader: 'Bearer ' + FAWA_KEY, sameOriginBase: FAWA_BASE };
 
 
 
 
 
6401
  try {
6402
- const r = await fetch(url, { headers: { Authorization: opts.authHeader } });
6403
  const ct = (r.headers.get('content-type') || '').toLowerCase();
6404
  if (!r.ok) throw new Error(T('err_generic') + ' ' + r.status);
6405
 
 
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">
 
1479
  // fawa-live (mechi/michezo moja kwa moja) — function tofauti, key ile ile
1480
  const FAWA_BASE = (JM_CONFIG.FAWA_URL || JM_CONFIG.CONTENT_URL) + '/functions/v1/fawa-live';
1481
  const FAWA_KEY = JM_CONFIG.FAWA_KEY || SUPABASE_ANON_KEY;
1482
+ // Seva ya akiba (Supabase) — inatumika TU pale Worker (Cloudflare) inaposhindwa kwa chaneli fulani
1483
+ const FAWA_BASE_FB = JM_CONFIG.FAWA_FALLBACK_URL ? JM_CONFIG.FAWA_FALLBACK_URL + '/functions/v1/fawa-live' : null;
1484
 
1485
  // jaynes-max-tv (LIVE TV — channel za kawaida, DASH/HLS + ClearKey) — function yetu wenyewe
1486
  const JAYNES_MAX_TV_BASE = JM_CONFIG.CONTENT_URL + '/functions/v1/jaynes-max-tv';
 
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
  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
  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
  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
  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
  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;
 
6343
 
6344
  async function openFawaMatch(item) {
6345
  openPlayerShell(item.title);
6346
+ // Jaribu Worker (Cloudflare) kwanza; ikishindwa (mf. 502) jaribu Supabase
6347
+ let url, opts, r = null;
6348
+ for (const b of [FAWA_BASE, FAWA_BASE_FB].filter(Boolean)) {
6349
+ url = b + '/proxy?slug=' + encodeURIComponent(item.id);
6350
+ opts = { authHeader: 'Bearer ' + FAWA_KEY, sameOriginBase: b };
6351
+ try { r = await fetch(url, { headers: { Authorization: opts.authHeader } }); if (r.ok) break; } catch (_) { r = null; }
6352
+ }
6353
  try {
6354
+ if (!r) throw new Error(T('err_generic'));
6355
  const ct = (r.headers.get('content-type') || '').toLowerCase();
6356
  if (!r.ok) throw new Error(T('err_generic') + ' ' + r.status);
6357