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