PageSourceSearch

https://gomoviestv.cyou/assets/v2.js?v=a5

js gomoviestv.cyou collected 2026-10-10 01:14:20 UTC 14,036 bytes, 226 lines download raw bytes

1const site = document.body.dataset.site || '';
2const base = `/${site}`;
3const catalogNode = document.querySelector('#catalog-data');
4const catalog = catalogNode ? JSON.parse(catalogNode.textContent || '[]') : [];
5const esc = (value = '') => String(value).replace(/[&<>\"]/g, (character) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' })[character]);
6const slugify = (value = '') => String(value).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
7const listKey = `${site}:watchlist:v3`;
8const readList = () => {
9  try {
10    const ids = JSON.parse(localStorage.getItem(listKey) || '[]');
11    return Array.isArray(ids) ? [...new Set(ids.filter(id => typeof id === 'string' && id))] : [];
12  } catch { return []; }
13};
14let saveNoticeTimer;
15function showSaveError() {
16  let notice = document.querySelector('[data-save-notice]');
17  if (!notice) {
18    notice = document.createElement('div');
19    notice.dataset.saveNotice = '';
20    notice.setAttribute('role', 'status');
21    notice.style.cssText = 'position:fixed;bottom:16px;left:16px;right:16px;max-width:460px;margin:auto;padding:14px 18px;background:#182127;color:#fff;border:1px solid #73818a;border-radius:8px;z-index:10000;font:16px/1.5 sans-serif';
22    document.body.append(notice);
23  }
24  notice.textContent = 'Your browser could not save this change. Check its storage settings.';
25  notice.hidden = false;
26  clearTimeout(saveNoticeTimer);
27  saveNoticeTimer = setTimeout(() => { notice.hidden = true; }, 6000);
28}
29const writeList = (ids) => {
30  try { localStorage.setItem(listKey, JSON.stringify([...new Set(ids)])); return true; }
31  catch { showSaveError(); return false; }
32};
33
34const menu = document.querySelector('[data-menu]');
35const menuNav = document.querySelector('.navlinks');
36function closeNavigation() {
37  menu?.setAttribute('aria-expanded', 'false');
38  menu?.setAttribute('aria-label', 'Open navigation');
39  menuNav?.classList.remove('open');
40}
41menu?.addEventListener('click', () => {
42  const open = menu.getAttribute('aria-expanded') !== 'true';
43  menu.setAttribute('aria-expanded', String(open));
44  menu.setAttribute('aria-label', open ? 'Close navigation' : 'Open navigation');
45  menuNav?.classList.toggle('open', open);
46});
47document.addEventListener('keydown', (event) => {
48  if (event.key === 'Escape' && menu?.getAttribute('aria-expanded') === 'true') {
49    closeNavigation();
50    menu.focus();
51  }
52});
53document.addEventListener('click', (event) => {
54  if (!menu?.contains(event.target) && !menuNav?.contains(event.target)) closeNavigation();
55});
56menuNav?.addEventListener('click', (event) => { if (event.target.closest('a')) closeNavigation(); });
57window.addEventListener('resize', () => { if (menu && getComputedStyle(menu).display === 'none') closeNavigation(); });
58
59document.querySelectorAll('[data-search]').forEach((form) => form.addEventListener('submit', (event) => {
60  event.preventDefault();
61  const query = String(new FormData(form).get('q') || '').trim();
62  location.href = `${base}/search/?q=${encodeURIComponent(query)}`;
63}));
64
65function toolCard(title, label = '') {
66  const rating = Number(title.rating) > 0 && Number(title.voteCount) > 0 ? ` · ★ ${Number(title.rating).toFixed(1)}` : '';
67  const active = readList().includes(title.id);
68  return `<article class="tool-card"><a href="${title.path}"><img src="${esc(title.poster)}" alt="${esc(title.title)} poster" width="172" height="258"><div>${label ? `<small>${esc(label)}</small>` : ''}<h3>${esc(title.title)}</h3><p>${esc(title.year)} · ${esc(title.genre)}${rating}</p></div></a><button type="button" data-watch-id="${esc(title.id)}" class="${active ? 'saved' : ''}" aria-pressed="${active}" aria-label="${active ? 'Remove' : 'Save'} ${esc(title.title)} ${active ? 'from' : 'to'} My List">${active ? '✓ Saved' : '+ Save'}</button></article>`;
69}
70
71const output = document.querySelector('[data-tool-output]');
72const tool = document.querySelector('[data-tool]');
73let toolTurn = 0;
74function runTool() {
75  if (!output || !catalog.length) return;
76  const values = tool ? new FormData(tool) : new FormData();
77  const mood = String(values.get('mood') || '');
78  const format = String(values.get('format') || '');
79  const genre = String(values.get('genre') || '');
80  const windowValue = String(values.get('window') || '');
81  const mode = tool?.dataset.toolMode || 'mix';
82  const genreMap = { bold: ['Action', 'Adventure', 'Thriller'], warm: ['Comedy', 'Family', 'Romance'], strange: ['Fantasy', 'Science Fiction', 'Mystery'], serious: ['Drama', 'History', 'Crime'] };
83  let pool = catalog.filter((title) => !format || title.type === format).filter((title) => !mood || title.genres.some((item) => genreMap[mood]?.includes(item))).filter((title) => !genre || title.genres.some((item) => slugify(item) === genre));
84  if (windowValue || ['today', 'broadcast'].includes(mode)) pool.sort((a, b) => String(b.releaseDate || '').localeCompare(String(a.releaseDate || '')));
85  if (mode === 'radar' || mode === 'route') pool.sort((a, b) => Number(b.popularity || 0) - Number(a.popularity || 0));
86  if (mode === 'compare') pool.sort((a, b) => (Number(b.rating || 0) * Math.log10(Number(b.voteCount || 0) + 1)) - (Number(a.rating || 0) * Math.log10(Number(a.voteCount || 0) + 1)));
87  if (!pool.length) pool = catalog;
88  const count = mode === 'double' ? 2 : 3;
89  const selected = Array.from({ length: count }, (_, index) => pool[(toolTurn + index * 5) % pool.length]);
90  const labels = mode === 'double' ? ['Feature presentation', 'Second half'] : mode === 'compare' ? ['Strongest signal', 'Different angle', 'Wildcard'] : mode === 'queue' ? ['Tonight', 'Weekend', 'Later'] : mode === 'three' ? ['Choice 1', 'Choice 2', 'Choice 3'] : ['First choice', 'Second angle', 'Keep for later'];
91  output.innerHTML = selected.map((title, index) => toolCard(title, labels[index])).join('');
92  toolTurn += 1;
93}
94tool?.addEventListener('submit', (event) => { event.preventDefault(); runTool(); });
95tool?.addEventListener('change', runTool);
96document.querySelector('[data-run-tool]')?.addEventListener('click', runTool);
97runTool();
98
99const instant = document.querySelector('[data-instant]');
100if (instant && output) {
101  const renderInstant = () => {
102    const query = instant.value.trim().toLowerCase();
103    const results = (query ? catalog.filter((title) => [title.title, title.year, title.originalLanguage, ...title.genres].join(' ').toLowerCase().includes(query)) : catalog).slice(0, 12);
104    output.innerHTML = results.map((title) => toolCard(title)).join('');
105  };
106  instant.addEventListener('input', renderInstant);
107  renderInstant();
108}
109
110const filter = document.querySelector('[data-filter]');
111if (filter) {
112  const initial = new URLSearchParams(location.search);
113  for (const name of ['q', 'genre', 'year']) {
114    const field = filter.elements.namedItem(name);
115    const value = initial.get(name);
116    if (field && value !== null && (name === 'q' || [...field.options].some(option => option.value === value))) field.value = value;
117  }
118  const apply = () => {
119    const values = new FormData(filter);
120    const query = String(values.get('q') || '').toLowerCase().trim();
121    const genre = String(values.get('genre') || '');
122    const year = String(values.get('year') || '');
123    let count = 0;
124    document.querySelectorAll('[data-catalog] > div').forEach((node) => {
125      const match = (!query || node.dataset.title.includes(query)) && (!genre || node.dataset.genre.split('|').includes(genre)) && (!year || node.dataset.year === year);
126      node.hidden = !match;
127      if (match) count += 1;
128    });
129    const countNode = document.querySelector('[data-result-count]');
130    if (countNode) countNode.textContent = `${count} matching title${count === 1 ? '' : 's'}`;
131    const empty = document.querySelector('[data-empty]');
132    if (empty) empty.hidden = count > 0;
133    const params = new URLSearchParams(location.search);
134    for (const name of ['q', 'genre', 'year']) {
135      const value = String(values.get(name) || '').trim();
136      if (value) params.set(name, value); else params.delete(name);
137    }
138    history.replaceState(null, '', `${location.pathname}${params.size ? `?${params}` : ''}${location.hash}`);
139  };
140  filter.addEventListener('input', apply);
141  filter.addEventListener('change', apply);
142  filter.addEventListener('submit', (event) => { event.preventDefault(); apply(); });
143  apply();
144}
145
146const searchForm = document.querySelector('[data-search-page]');
147const searchResults = document.querySelector('[data-search-results]');
148if (searchForm && searchResults) {
149  const field = searchForm.elements.q;
150  field.value = new URLSearchParams(location.search).get('q') || '';
151  const render = () => {
152    const query = field.value.trim().toLowerCase();
153    const results = query ? catalog.filter((title) => [title.title, title.originalTitle, title.year, title.originalLanguage, ...title.genres].join(' ').toLowerCase().includes(query)).slice(0, 80) : [];
154    searchResults.innerHTML = results.map((title) => toolCard(title)).join('');
155    const count = document.querySelector('[data-search-count]');
156    if (count) count.textContent = query ? `${results.length} result${results.length === 1 ? '' : 's'} for “${field.value.trim()}”` : `Search ${catalog.length} movies and TV shows.`;
157  };
158  const updateSearch = () => {
159    const query = field.value.trim();
160    history.replaceState(null, '', `${location.pathname}${query ? `?q=${encodeURIComponent(query)}` : ''}${location.hash}`);
161    render();
162  };
163  searchForm.addEventListener('submit', (event) => { event.preventDefault(); updateSearch(); });
164  searchForm.addEventListener('input', updateSearch);
165  render();
166}
167
168const providerNode = document.querySelector('#provider-data');
169const region = document.querySelector('[data-region]');
170const providerOutput = document.querySelector('[data-providers]');
171if (providerNode && region && providerOutput) {
172  const providerData = JSON.parse(providerNode.textContent || '{}');
173  const labels = { free: 'Free', ads: 'With ads', subscription: 'Subscription', rent: 'Rent', buy: 'Buy' };
174  const renderProviders = () => {
175    const selected = providerData.providers?.[region.value];
176    const selectedCountry = providerData.countries?.[region.value] || 'us';
177    const country = selectedCountry === 'gb' ? 'uk' : selectedCountry;
178    const justWatchUrl = `https://gomoviestv.cyou/home/${country}/search?q=${encodeURIComponent(providerData.title || '')}`;
179    if (!selected) { providerOutput.innerHTML = `<div class="provider-empty"><p>Check current viewing options for this title in your country.</p><a class="action" href="${esc(justWatchUrl)}" rel="nofollow noopener" target="_blank">Search on JustWatch ↗</a></div>`; return; }
180    const groups = Object.entries(labels).filter(([key]) => selected[key]?.length);
181    providerOutput.innerHTML = groups.length ? `${groups.map(([key, label]) => `<section class="provider-group"><h3>${label}</h3><div>${selected[key].map((provider) => `<a href="${esc(justWatchUrl)}" rel="nofollow noopener" target="_blank">${provider.logo ? `<img src="${esc(provider.logo)}" alt="${esc(provider.name)} logo" width="46" height="46">` : ''}<span>${esc(provider.name)}</span><b>View on JustWatch ↗</b></a>`).join('')}</div></section>`).join('')}<p><a class="action" href="${esc(justWatchUrl)}" rel="nofollow noopener" target="_blank">See every option on JustWatch ↗</a></p>` : `<div class="provider-empty"><p>Check current viewing options for your country.</p><a class="action" href="${esc(justWatchUrl)}" rel="nofollow noopener" target="_blank">Search on JustWatch ↗</a></div>`;
182  };
183  region.addEventListener('change', renderProviders);
184}
185
186function refreshSaveButtons() {
187  const saved = new Set(readList());
188  document.querySelectorAll('[data-watch-id]').forEach((button) => {
189    const active = saved.has(button.dataset.watchId);
190    button.classList.toggle('saved', active);
191    button.textContent = active ? '✓ Saved' : '+ Save';
192    button.setAttribute('aria-pressed', String(active));
193    const title = catalog.find(item => item.id === button.dataset.watchId)?.title || 'title';
194    button.setAttribute('aria-label', `${active ? 'Remove' : 'Save'} ${title} ${active ? 'from' : 'to'} My List`);
195  });
196}
197document.addEventListener('click', (event) => {
198  const button = event.target.closest('[data-watch-id]');
199  if (!button) return;
200  const saved = new Set(readList());
201  saved.has(button.dataset.watchId) ? saved.delete(button.dataset.watchId) : saved.add(button.dataset.watchId);
202  if (!writeList([...saved])) return;
203  refreshSaveButtons();
204  renderWatchlist();
205});
206const watchlistOutput = document.querySelector('[data-watchlist-output]');
207function renderWatchlist() {
208  if (!watchlistOutput) return;
209  const saved = new Set(readList());
210  const matches = catalog.filter((title) => saved.has(title.id));
211  watchlistOutput.innerHTML = matches.map((title) =>
211 toolCard(title, 'Saved on this device')).join('');
212  const empty = document.querySelector('[data-watchlist-empty]');
213  if (empty) empty.hidden = matches.length > 0;
214}
215document.querySelector('[data-watchlist-clear]')?.addEventListener('click', () => { if (writeList([])) { refreshSaveButtons(); renderWatchlist(); } });
216window.addEventListener('storage', (event) => { if (event.key === listKey || event.key === null) { refreshSaveButtons(); renderWatchlist(); } });
217refreshSaveButtons();
218renderWatchlist();
219
220let deferredInstall;
221window.addEventListener('beforeinstallprompt', (event) => { event.preventDefault(); deferredInstall = event; document.querySelectorAll('[data-install-app]').forEach((button) => button.hidden = false); });
222document.querySelectorAll('[data-install-app]').forEach((button) => button.addEventListener('click', async () => {
223  if (deferredInstall) { deferredInstall.prompt(); await deferredInstall.userChoice; deferredInstall = null; }
224  else alert('Use your browser menu and choose Install app or Add to Home Screen.');
225}));
226if ('serviceWorker' in navigator) window.addEventListener('load', () => navigator.serviceWorker.register(`${base}/sw.js`).catch(() => {}));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.