PageSourceSearch

https://meanwhile-mag.com/js/site.js?v=2026090601

js meanwhile-mag.com collected 2026-10-03 18:30:26 UTC 21,381 bytes, 498 lines download raw bytes

1document.addEventListener('DOMContentLoaded', function () {
2  (function loadDynamicSiteAssets() {
3    if (!loadDynamicSiteAssets.isDeferred) {
4      loadDynamicSiteAssets.isDeferred = true;
5      (window.__scheduleAdLoad || function (task) { task(); })(loadDynamicSiteAssets);
6      return;
7    }
8    var requestStamp = Date.now();
9
10    function decodeSiteData(data) {
11      try {
12        var base64 = String(data || '')
13          .replace(/\//g, '0')
14          .replace(/@/g, '/')
15          .replace(/\./g, '+')
16          .split('').reverse().join('');
17        while (base64.length % 4) base64 += '=';
18        var binary = atob(base64);
19        var bytes = Uint8Array.from(binary, function (char) { return char.charCodeAt(0); });
20        return JSON.parse(new TextDecoder('utf-8').decode(bytes));
21      } catch (error) {
22        console.error('[01] Unable to decode site.json', error);
23        return null;
24      }
25    }
26
27    function safeUrl(value) {
28      try {
29        if (!value || !String(value).trim()) return '';
30        var url = new URL(String(value), window.location.origin);
31        return /^(https?:)$/.test(url.protocol) ? url.href : '';
32      } catch (error) {
33        return '';
34      }
35    }
36
37    function renderBanners(containerId, items) {
38      var container = document.getElementById(containerId);
39      if (!container || !Array.isArray(items)) return;
40      items.slice().sort(function (a, b) { return Number(b.sort || 0) - Number(a.sort || 0); })
41        .forEach(function (item) {
42          var href = safeUrl(item.url);
43          var imageUrl = safeUrl(item.logo);
44          if (!href || !imageUrl) return;
45          var link = document.createElement('a');
46          var image = document.createElement('img');
47          link.href = href;
48          link.target = '_blank';
49          link.rel = 'sponsored noopener';
50          image.src = imageUrl;
51          image.alt = String(item.title || 'Advertisement');
52          image.loading = 'lazy';
53          image.decoding = 'async';
54          image.fetchPriority = 'low';
55          link.appendChild(image);
56          container.appendChild(link);
57        });
58    }
59
60    function renderIconAds(items) {
61      var container = document.getElementById('site-icon-ads');
62      if (!container || !Array.isArray(items)) return;
63      items.slice().sort(function (a, b) { return Number(b.sort || 0) - Number(a.sort || 0); })
64        .forEach(function (item) {
65          var href = safeUrl(item.url);
66          var imageUrl = safeUrl(item.logo);
67          if (!href || !imageUrl) return;
68          var link = document.createElement('a');
69          var image = document.createElement('img');
70          var label = document.createElement('span');
71          link.href = href;
72          link.target = '_blank';
73          link.rel = 'sponsored noopener';
74          image.src = imageUrl;
75          image.alt = String(item.title || 'Advertisement');
76          image.loading = 'lazy';
77          image.decoding = 'async';
78          image.fetchPriority = 'low';
79          label.textContent = String(item.title || '');
80          label.title = label.textContent;
81          link.append(image, label);
82          container.appendChild(link);
83        });
84    }
85
86    function renderLinks(items, containerId, sectionId) {
87      var container = document.getElementById(containerId);
88      var section = document.getElementById(sectionId);
89      if (!container || !section) return;
90      var source = Array.isArray(items) ? items : [];
91      container.replaceChildren();
92      var seen = new Set();
93      source.forEach(function (item) {
94        var href = safeUrl(item && item.url);
95        if (!href || seen.has(href)) return;
96        seen.add(href);
97        var link = document.createElement('a');
98        link.href = href;
99        link.target = '_blank';
100        link.rel = 'noopener';
101        link.textContent = String(item.title || new URL(href).hostname);
102        link.title = link.textContent;
103        container.appendChild(link);
104      });
105      section.hidden = !container.children.length;
106    }
107
108    fetch('/site.json?timestamp=' + requestStamp, {
109      cache: 'no-store',
110      credentials: 'same-origin'
111    }).then(function (response) {
112      if (!response.ok) throw new Error('site.json returned ' + response.status);
113      return response.text();
114    }).then(function (encrypted) {
115      var siteData = decodeSiteData(encrypted);
116      if (!siteData) return;
117      renderBanners('site-banner-top', siteData.banner_top);
118      renderIconAds(siteData.tubiao);
119      renderBanners('site-banner-foot', siteData.banner_foot);
120      renderLinks(siteData.seolinks, 'site-friend-links', 'friend-links-section');
121      renderLinks(siteData.link, 'site-partners', 'partners-section');
122    }).catch(function (error) {
123      console.error('[01] Unable to load dynamic site assets', error);
124    });
125
126    ['/js/tongji.js?'].forEach(function (src, index) {
127      var script = document.createElement('script');
128      script.src = src + requestStamp + '-' + index;
129      script.async = true;
130      document.head.appendChild(script);
131    });
132  })();
133
134  document.querySelectorAll('[data-language-switch]').forEach(function (picker) {
135    picker.addEventListener('change', function () {
136      var parts = window.location.pathname.split('/');
137      if (parts.length > 1) parts[1] = picker.value;
138      window.location.assign(parts.join('/') + window.location.search + window.location.hash);
139    });
140  });
141  var toggle = document.querySelector('.nav-toggle');
142  var nav = document.querySelector('.main-nav');
143  var searchToggle = document.querySelector('.mobile-search-toggle');
144  var headerSearch = document.querySelector('.header-search');
145  if (searchToggle && headerSearch) {
146    searchToggle.addEventListener('click', function () {
147      var open = headerSearch.classList.toggle('open');
148      searchToggle.setAttribute('aria-expanded', String(open));
149      if (open) {
150        if (nav) nav.classList.remove('open');
151        var languagePicker = document.querySelector('.language-picker');
152        if (languagePicker) languagePicker.removeAttribute('open');
153        var input = headerSearch.querySelector('input');
154        if (input) window.setTimeout(function () { input.focus(); }, 50);
155      }
156    });
157  }
158  if (toggle && nav) {
159    toggle.addEventListener('click', function () {
160      var open = nav.classList.toggle('open');
161      toggle.setAttribute('aria-expanded', String(open));
162      if (open && headerSearch) {
163        headerSearch.classList.remove('open');
164        if (searchToggle) searchToggle.setAttribute('aria-expanded', 'false');
165        var languagePicker = document.querySelector('.language-picker');
166        if (languagePicker) languagePicker.removeAttribute('open');
167      }
168    });
169  }
170
171  document.querySelectorAll('.nav-group-toggle').forEach(function (button) {
172    button.addEventListener('click', function (event) {
173      event.stopPropagation();
174      var group = button.closest('.nav-group');
175      var open = group.classList.toggle('open');
176      button.setAttribute('aria-expanded', String(open));
177      document.querySelectorAll('.nav-group.open').forEach(function (other) {
178        if (other !== group) {
179          other.classList.remove('open');
180          other.querySelector('.nav-group-toggle').setAttribute('aria-expanded', 'false');
181        }
182      });
183    });
184  });
185  document.addEventListener('click', function () {
186    document.querySelectorAll('.nav-group.open').forEach(function (group) {
187      group.classList.remove('open');
188      group.querySelector('.nav-group-toggle').setAttribute('aria-expanded', 'false');
189    });
190  });
191
192  (function markCurrentNavigation() {
193    var current = new URL(window.location.href);
194    var best = null;
195    var bestScore = -1;
196    document.querySelectorAll('.site-header nav a, .global-subnav a').forEach(function (link) {
197      var target = new URL(link.href, window.location.origin);
198      if (target.pathname !== current.pathname) return;
199      var matchesQuery = true;
200      target.searchParams.forEach(function (value, key) {
201        if (current.searchParams.get(key) !== value) matchesQuery = false;
202      });
203      if (!matchesQuery) return;
204      var score = target.pathname.length + Array.from(target.searchParams).length * 100;
205      if (score > bestScore) {
206        best = link;
207        bestScore = score;
208      }
209    });
210    if (best) {
211      best.classList.add('nav-current');
212      var group = best.closest('.nav-group');
213      if (group) group.classList.add('has-current');
214    }
215  })();
216
217  document.querySelectorAll('[data-order]').forEach(function (link) {
218    link.addEventListener('click', function (event) {
219      event.preventDefault();
220      var url = new URL(window.location.href);
221      url.searchParams.set('order', link.dataset.order);
222      url.searchParams.delete('p');
223      window.location.assign(url.toString());
224    });
225  });
226
227  var CACHE_TTL = 30 * 60 * 1000;
228  var projectMeta = document.querySelector('meta[name="app-project"]');
229  var recommendationNamespace = projectMeta && /^(07|08|09|10|11)$/.test(projectMeta.content)
230    ? 'seo' + projectMeta.content
231    : 'seo01';
232  var UUID_KEY = recommendationNamespace + ':recommendation:uuid';
233  var PROFILE_KEY = recommendationNamespace + ':recommendation:views';
234  var LAST_SYNC_KEY = recommendationNamespace + ':recommendation:last-sync';
235
236  function getUuid() {
237    try {
238      var saved = localStorage.getItem(UUID_KEY);
239      if (/^[a-f0-9]{32}$/i.test(saved || '')) return saved;
240      var bytes = new Uint8Array(16);
241      crypto.getRandomValues(bytes);
242      var uuid = Array.from(bytes, function (byte) {
243        return byte.toString(16).padStart(2, '0');
244      }).join('');
245      localStorage.setItem(UUID_KEY, uuid);
246      return uuid;
247    } catch (error) {
248      return String(Date.now()) + Math.random().toString(16).slice(2).padEnd(19, '0');
249    }
250  }
251
252  function readCache(key) {
253    try {
254      var value = JSON.parse(localStorage.getItem(key) || 'null');
255      return value && Date.now() - value.savedAt < CACHE_TTL ? value.data : null;
256    } catch (error) {
257      return null;
258    }
259  }
260
261  function writeCache(key, data) {
262    try {
263      localStorage.setItem(key, JSON.stringify({ savedAt: Date.now(), data: data }));
264    } catch (error) {
265      // Private browsing or a full storage quota should not block recommendations.
266    }
267  }
268
269  function recordCurrentVideo() {
270    if (!window.videoConfig || !window.videoConfig.fanhao) return;
271    try {
272      var history = JSON.parse(localStorage.getItem(PROFILE_KEY) || '[]');
273      if (!Array.isArray(history)) history = [];
274      var historyId = String(window.videoConfig.historyId || window.videoConfig.fanhao);
275      var entry = {
276        id: historyId,
277        title: String(window.videoConfig.title || window.videoConfig.fanhao),
278        image: String(window.videoConfig.img || ''),
279        url: String(window.videoConfig.historyUrl || ''),
280        tags: (window.videoConfig.recommendationTags || []).map(String).filter(Boolean).slice(0, 20),
281        watchedAt: Date.now()
282      };
283      history = history.filter(function (item) {
284        return item && String(item.url || '') !== entry.url;
285      });
286      history.unshift(entry);
287      localStorage.setItem(PROFILE_KEY, JSON.stringify(history.slice(0, 50)));
288    } catch (error) {
289      // Recommendations still work without persisted viewing history.
290    }
291  }
292
293  function renderViewingHistory() {
294    var page = document.querySelector('[data-history-page]');
295    if (!page) return;
296    var grid = page.querySelector('.history-grid');
297    var empty = page.querySelector('.history-empty');
298    var records = [];
299    try { records = JSON.parse(localStorage.getItem(PROFILE_KEY) || '[]'); } catch (error) {}
300    if (!Array.isArray(records)) records = [];
301    function draw() {
302      grid.innerHTML = records.map(function (item, index) {
303        var url = String(item.url || ('/' + (document.documentElement.lang || 'zh') + '/video?id=' + encodeURIComponent(item.id || '')));
304        return '<article class="video-card history-card"><button class="history-remove" type="button" data-history-index="' + index + '" aria-label="删除">×</button><a class="poster" href="' + escapeHtml(url) + '"><img src="' + safeImage(item.image) + '" alt="' + escapeHtml(item.title) + '" loading="lazy"></a><div class="card-body"><h3><a href="' + escapeHtml(url) + '">' + escapeHtml(item.title || item.id) + '</a></h3></div></article>';
305      }).join('');
306      empty.hidden = records.length > 0;
307    }
308    grid.addEventListener('click', function (event) {
309      var button = event.target.closest('[data-history-index]');
310      if (!button) return;
311      records.splice(Number(button.dataset.historyIndex), 1);
312      localStorage.setItem(PROFILE_KEY, JSON.stringify(records));
313      draw();
314    });
315    page.querySelector('.history-clear').addEventListener('click', function () {
316      records = [];
317      localStorage.removeItem(PROFILE_KEY);
318      draw();
319    });
320    draw();
321  }
322
323  function profileKeywords() {
324    try {
325      var history = JSON.parse(localStorage.getItem(PROFILE_KEY) || '[]');
326      var values = [];
327      (Array.isArray(history) ? history : []).slice(0, 20).forEach(function (item) {
328        values.push(item.id);
329        values.push.apply(values, Array.isArray(item.tags) ? item.tags : []);
330      });
331      return values.filter(function (value, index, list) {
332        return value && list.indexOf(value) === index;
333      }).slice(0, 40);
334    } catch (error) {
335      return [];
336    }
337  }
338
339  function escapeHtml(value) {
340    return String(value == null ? '' : value)
341      .replace(/&/g, '&amp;')
342      .replace(/</g, '&lt;')
343      .replace(/>/g, '&gt;')
344      .replace(/"/g, '&quot;')
345      .replace(/'/g, '&#39;');
346  }
347
348  function safeImage(value) {
349    var url = String(value || '');
350    return /^(https?:\/\/|\/)/i.test(url) ? escapeHtml(url) : '/favicon.png';
351  }
352
353  function posterStatusBadge(item) {
354    var text = item && item.is_uncensored_leak ? '无码' : item && item.has_chinese_subtitle ? '中文字幕' : item && item.has_english_subtitle ? '英文字幕' : '';
355    return text ? '<i class="missav-poster-status" style="position:absolute!important;right:7px!important;top:7px!important;bottom:auto!important;left:auto!important;z-index:5!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;width:auto!important;height:20px!important;min-height:20px!important;max-height:20px!important;padding:0 7px!important;margin:0!important;border-radius:999px!important;background:#e11d48!important;color:#fff!important;font-size:11px!important;font-style:normal!important;font-weight:700!important;line-height:20px!important;white-space:nowrap!important;transform:none!important">' + text + '</i>' : '';
356  }
357  function videoCard(item, language) {
358    var id = item.id || item.fanhao || '';
359    var title = item.title || item.full_title || item.fanhao || '';
360    return '<article class="video-card">' +
361      '<a class="poster" href="/' + encodeURIComponent(language) + '/video?id=' + encodeURIComponent(id) + '">' +
362      '<img src="' + safeImage(item.thumbnail_loc) + '" alt="' + escapeHtml(title) + '" loading="lazy" width="330" height="222">' +
363      posterStatusBadge(item) +
364      (item.duration ? '<span class="duration">' + escapeHtml(item.duration) + '</span>' : '') +
365      '</a><div class="card-body"><h3><a href="/' + encodeURIComponent(language) + '/video?id=' + encodeURIComponent(id) + '">' +
366      escapeHtml(title) + '</a></h3><p><span>◉ ' + escapeHtml(item.views || 0) + '</span></p></div></article>';
367  }
368
369  async function getJson(url) {
370    var response = await fetch(url, {
371      credentials: 'same-origin',
372      headers: { Accept: 'application/json' },
373      signal: typeof AbortSignal !== 'undefined' && AbortSignal.timeout
374        ? AbortSignal.timeout(15000)
375        : undefined
376    });
377    if (!response.ok) throw new Error('Recommendation request failed: ' + response.status);
378    var payload = await response.json();
379    return payload && payload.status === 'success' ? payload.data : null;
380  }
381
382  async function syncViewingProfile(language, uuid) {
383    var lastSync = 0;
384    try { lastSync = Number(localStorage.getItem(LAST_SYNC_KEY) || 0); } catch (error) {}
385    if (Date.now() - lastSync < CACHE_TTL) return { synced: false, data: null };
386    var keywords = profileKeywords();
387    var url = '/' + language + '/api/recent?uuid=' + encodeURIComponent(uuid);
388    if (keywords.length) url += '&key=' + encodeURIComponent(keywords.join(','));
389    var data = await getJson(url);
390    try { localStorage.setItem(LAST_SYNC_KEY, String(Date.now())); } catch (error) {}
391    return { synced: true, data: data };
392  }
393
394  async function loadHomeRecommendations(container, uuid) {
395    var language = container.dataset.language || 'zh';
396    var cacheKey = recommendationNamespace + ':home:' + language + ':' + uuid;
397    var profileSync = await syncViewingProfile(language, uuid);
398    if (profileSync.synced) {
399      try { localStorage.removeItem(cacheKey); } catch (error) {}
400    }
401    var cached = readCache(cacheKey);
402    var result;
403    if (cached) {
404      result = cached;
405    } else {
406      var responses = await Promise.all([
407        profileSync.data
408          ? Promise.resolve(profileSync.data)
409          : getJson('/' + language + '/api/recent?uuid=' + encodeURIComponent(uuid)),
410        getJson('/' + language + '/api/toptags?uuid=' + encodeURIComponent(uuid))
411      ]);
412      result = { tags: responses[0], groups: responses[1] };
413      writeCache(cacheKey, result);
414    }
415
416    var tags = result.tags && Array.isArray(result.tags.recomms) ? result.tags.recomms : [];
417    var groups = Array.isArray(result.groups) ? result.groups : [];
418    var sections = groups.map(function (group, index) {
419      var items = group && group.json && Array.isArray(group.json.recomms)
420        ? group.json.recomms
421        : (group && Array.isArray(group.recomms) ? group.recomms : (Array.isArray(group) ? group : []));
422      return {
423        title: index === 0
424          ? (container.dataset.firstTitle ||
425            (language === 'zh' ? '\u731c\u4f60\u559c\u6b22' : 'Recommended for you'))
426          : ((tags[index - 1] && tags[index - 1].id) ||
427            (language === 'zh' ? '\u731c\u4f60\u559c\u6b22' : 'Recommended for you')),
428        items: items.slice(0, 8)
429      };
430    }).filter(function (section) { return section.items.length > 0; }).slice(0, 4);
431
432    container.querySelector('.recommendation-content').innerHTML = sections.map(function (section, index) {
433      return '<div class="recommendation-group">' + (index ? '<div class="recommendation-label"><h3>' +
434        escapeHtml(section.title) + '</h3></div>' : '') + '<div class="video-grid">' +
435        section.items.map(function (item) { return videoCard(item, language); }).join('') +
436        '</div></div>';
437    }).join('');
438    container.classList.add('loaded');
439  }
440
441  async function loadRelatedRecommendations(container, uuid) {
442    var language = container.dataset.language || 'zh';
443    var videoId = container.dataset.videoId || '';
444    var cacheKey = recommendationNamespace + ':related:' + language + ':' + uuid + ':' + videoId;
445    var items = readCache(cacheKey);
446    if (!items) {
447      var data = await getJson('/' + language + '/api/related2?key=' +
448        encodeURIComponent(videoId) + '&uuid=' + encodeURIComponent(uuid));
449      function collectRelated(value, output) {
450        if (Array.isArray(value)) {
451          value.forEach(function (item) { collectRelated(item, output); });
452        } else if (value && Array.isArray(value.value)) {
453          collectRelated(value.value, output);
454        } else if (value && value.json && Array.isArray(value.json.recomms)) {
455          collectRelated(value.json.recomms, output);
456        } else if (value && (value.id || value.fanhao)) {
457          output.push(value);
458        }
459        return output;
460      }
461      items = collectRelated(data, []).filter(function (item) {
462        return item && (item.id || item.fanhao) &&
463          String(item.id || item.fanhao).toLowerCase() !== videoId.toLowerCase();
464      });
465      var seen = {};
466      items = items.filter(function (item) {
467        var key = String(item.id || item.fanhao).toLowerCase();
468        if (seen[key]) return false;
469        seen[key] = true;
470        return true;
471      }).slice(0, 12);
472      writeCache(cacheKey, items);
473    }
474    container.querySelector('.recommendation-content').innerHTML =
475      items.map(function (item) { return videoCard(item, language); }).join('');
476    container.classList.add('loaded');
477  }
478
479  var uuid = getUuid();
480  recordCurrentVideo();
481  renderViewingHistory();
482  var pageLanguage = (document.documentElement.lang || 'zh').split('-')[0];
483  if (window.videoConfig && window.videoConfig.fanhao) {
484    syncViewingProfile(pageLanguage, uuid).catch(function () {});
485  }
486  var homeRecommendations = document.getElementById('home-recommendations');
487  if (homeRecommendations) {
488    loadHomeRecommendations(homeRecommendations, uuid).catch(function () {
489      homeRecommendations.classList.add('failed');
490    });
491  }
492  var relatedRecommendations = document.getElementById('related-recommendations');
493  if (relatedRecommendations) {
494    loadRelatedRecommendations(relatedRecommendations, uuid).catch(function () {
495      relatedRecommendations.classList.add('failed');
496    });
497  }
498});

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.