1// è±ç£çé¨çµå½±çµè§å§æ¨èåè½ 2 3// è±ç£æ ç¾å表 - ä¿®æ¹ä¸ºé»è®¤æ ç¾ 4let defaultMovieTags = ['çé¨', 'ææ°', 'ç»å ¸', 'è±ç£é«å', 'å·é¨ä½³ç', 'åè¯', '欧ç¾', 'é©å½', 'æ¥æ¬', 'å¨ä½', 'åå§', 'ç±æ ', 'ç§å¹»', 'æ¬ç', 'ææ', 'æ²»æ']; 5let defaultTvTags = ['çé¨', 'ç¾å§', 'è±å§', 'é©å§', 'æ¥å§', 'å½äº§å§', '港å§', 'æ¥æ¬å¨ç»', '综èº', '纪å½ç']; 6 7// ç¨æ·æ ç¾å表 - åå¨ç¨æ·å®é 使ç¨çæ ç¾ï¼å å«ä¿ççç³»ç»æ ç¾åç¨æ·æ·»å çèªå®ä¹æ ç¾ï¼ 8let movieTags = []; 9let tvTags = []; 10 11// å è½½ç¨æ·æ ç¾ 12function loadUserTags() { 13 try { 14 // å°è¯ä»æ¬å°åå¨å è½½ç¨æ·ä¿åçæ ç¾ 15 const savedMovieTags = localStorage.getItem('userMovieTags'); 16 const savedTvTags = localStorage.getItem('userTvTags'); 17 18 // 妿æ¬å°åå¨ä¸ææ ç¾æ°æ®ï¼å使ç¨å® 19 if (savedMovieTags) { 20 movieTags = JSON.parse(savedMovieTags); 21 } else { 22 // å¦å使ç¨é»è®¤æ ç¾ 23 movieTags = [...defaultMovieTags]; 24 } 25 26 if (savedTvTags) { 27 tvTags = JSON.parse(savedTvTags); 28 } else { 29 // å¦å使ç¨é»è®¤æ ç¾ 30 tvTags = [...defaultTvTags]; 31 } 32 } catch (e) { 33 console.error('å è½½æ ç¾å¤±è´¥ï¼', e); 34 // åå§å为é»è®¤å¼ï¼é²æ¢é误 35 movieTags = [...defaultMovieTags]; 36 tvTags = [...defaultTvTags]; 37 } 38} 39 40// ä¿åç¨æ·æ ç¾ 41function saveUserTags() { 42 try { 43 localStorage.setItem('userMovieTags', JSON.stringify(movieTags)); 44 localStorage.setItem('userTvTags', JSON.stringify(tvTags)); 45 } catch (e) { 46 console.error('ä¿åæ ç¾å¤±è´¥ï¼', e); 47 showToast('ä¿åæ ç¾å¤±è´¥', 'error'); 48 } 49} 50 51let doubanMovieTvCurrentSwitch = 'movie'; 52let doubanCurrentTag = 'çé¨'; 53let doubanPageStart = 0; 54const doubanPageSize = 16; // 䏿¬¡æ¾ç¤ºçé¡¹ç®æ°é 55 56// åå§åè±ç£åè½ 57function initDouban() { 58 // 设置è±ç£å¼å ³çåå§ç¶æ 59 const doubanToggle = document.getElementById('doubanToggle'); 60 if (doubanToggle) { 61 const isEnabled = localStorage.getItem('doubanEnabled') === 'true'; 62 doubanToggle.checked = isEnabled; 63 64 // 设置å¼å ³å¤è§ 65 const toggleBg = doubanToggle.nextElementSibling; 66 const toggleDot = toggleBg.nextElementSibling; 67 if (isEnabled) { 68 toggleBg.classList.add('bg-pink-600'); 69 toggleDot.classList.add('translate-x-6'); 70 } 71 72 // æ·»å äºä»¶çå¬ 73 doubanToggle.addEventListener('change', function(e) { 74 const isChecked = e.target.checked; 75 localStorage.setItem('doubanEnabled', isChecked); 76 77 // æ´æ°å¼å ³å¤è§ 78 if (isChecked) { 79 toggleBg.classList.add('bg-pink-600'); 80 toggleDot.classList.add('translate-x-6'); 81 } else { 82 toggleBg.classList.remove('bg-pink-600'); 83 toggleDot.classList.remove('translate-x-6'); 84 } 85 86 // æ´æ°æ¾ç¤ºç¶æ 87 updateDoubanVisibility(); 88 }); 89 90 // åå§æ´æ°æ¾ç¤ºç¶æ 91 updateDoubanVisibility(); 92 93 // æ»å¨å°é¡µé¢é¡¶é¨ 94 window.scrollTo(0, 0); 95 } 96 97 // å è½½ç¨æ·æ ç¾ 98 loadUserTags(); 99 100 // 渲æçµå½±/çµè§å§åæ¢ 101 renderDoubanMovieTvSwitch(); 102 103 // 渲æè±ç£æ ç¾ 104 renderDoubanTags(); 105 106 // æ¢ä¸æ¹æé®äºä»¶çå¬ 107 setupDoubanRefreshBtn(); 108 109 // åå§å è½½çé¨å 容 110 if (localStorage.getItem('doubanEnabled') === 'true') { 111 renderRecommend(doubanCurrentTag, doubanPageSize, doubanPageStart); 112 } 113} 114 115// æ ¹æ®è®¾ç½®æ´æ°è±ç£åºåçæ¾ç¤ºç¶æ 116function updateDoubanVisibility() { 117 const doubanArea = document.getElementById('doubanArea'); 118 if (!doubanArea) return; 119 120 const isEnabled = localStorage.getItem('doubanEnabled') === 'true'; 121 const isSearching = document.getElementById('resultsArea') && 122 !document.getElementById('resultsArea').classList.contains('hidden'); 123 124 // åªæå¨å¯ç¨ä¸æ²¡ææç´¢ç»ææ¾ç¤ºæ¶ææ¾ç¤ºè±ç£åºå 125 if (isEnabled && !isSearching) { 126 doubanArea.classList.remove('hidden'); 127 // 妿è±ç£ç»æä¸ºç©ºï¼éæ°å è½½ 128 if (document.getElementById('douban-results').children.length === 0) { 129 renderRecommend(doubanCurrentTag, doubanPageSize, doubanPageStart); 130 } 131 } else { 132 doubanArea.classList.add('hidden'); 133 } 134} 135 136// åªå¡«å æç´¢æ¡ï¼ä¸æ§è¡æç´¢ï¼è®©ç¨æ·èªä¸»å³å®æç´¢æ¶æº 137function fillSearchInput(title) { 138 if (!title) return; 139 140 // å®å ¨å¤çæ é¢ï¼é²æ¢XSS 141 const safeTitle = title 142 .replace(/</g, '<') 143 .replace(/>/g, '>') 144 .replace(/"/g, '"'); 145 146 const input = document.getElementById('searchInput'); 147 if (input) { 148 input.value = safeTitle; 149 150 // èç¦æç´¢æ¡ï¼ä¾¿äºç¨æ·ç«å³ä½¿ç¨é®çæä½ 151 input.focus(); 152 153 // æ¾ç¤ºä¸ä¸ªæç¤ºï¼åç¥ç¨æ·ç¹å»æç´¢æé®è¿è¡æç´¢ 154 showToast('å·²å¡«å æç´¢å 容ï¼ç¹å»æç´¢æé®å¼å§æç´¢', 'info'); 155 } 156} 157 158// å¡«å æç´¢æ¡å¹¶æ§è¡æç´¢ 159function fillAndSearch(title) { 160 if (!title) return; 161 162 // å®å ¨å¤çæ é¢ï¼é²æ¢XSS 163 const safeTitle = title 164 .replace(/</g, '<') 165 .replace(/>/g, '>') 166 .replace(/"/g, '"'); 167 168 const input = document.getElementById('searchInput'); 169 if (input) { 170 input.value = safeTitle; 171 search(); // 使ç¨å·²æçsearch彿°æ§è¡æç´¢ 172 173 // åæ¶æ´æ°æµè§å¨URLï¼ä½¿å ¶åæ å½åçæç´¢ç¶æ 174 try { 175 // 使ç¨URIç¼ç ç¡®ä¿ç¹æ®å符è½å¤æ£ç¡®æ¾ç¤º 176 const encodedQuery = encodeURIComponent(safeTitle); 177 // 使ç¨HTML5 History APIæ´æ°URLï¼ä¸å·æ°é¡µé¢ 178 window.history.pushState( 179 { search: safeTitle }, 180 `æç´¢: ${safeTitle} - LibreTV`, 181 `/s=${encodedQuery}` 182 ); 183 // æ´æ°é¡µé¢æ é¢ 184 document.title = `æç´¢: ${safeTitle} - LibreTV`; 185 } catch (e) { 186 console.error('æ´æ°æµè§å¨åå²å¤±è´¥:', e); 187 } 188 } 189} 190 191// å¡«å æç´¢æ¡ï¼ç¡®ä¿è±ç£èµæºAPI被éä¸ï¼ç¶åæ§è¡æç´¢ 192async function fillAndSearchWithDouban(title) { 193 if (!title) return; 194 195 // å®å ¨å¤çæ é¢ï¼é²æ¢XSS 196 const safeTitle = title 197 .replace(/</g, '<') 198 .replace(/>/g, '>') 199 .replace(/"/g, '"'); 200 201 // ç¡®ä¿è±ç£èµæºAPI被éä¸ 202 if (typeof selectedAPIs !== 'undefined' && !selectedAPIs.includes('dbzy')) { 203 // å¨è®¾ç½®ä¸å¾éè±ç£èµæºAPIå¤éæ¡ 204 const doubanCheckbox = document.querySelector('input[id="api_dbzy"]'); 205 if (doubanCheckbox) { 206 doubanCheckbox.checked = true; 207 208 // 触åupdateSelectedAPIs彿°ä»¥æ´æ°ç¶æ 209 if (typeof updateSelectedAPIs === 'function') { 210 updateSelectedAPIs(); 211 } else { 212 // 妿彿°ä¸å¯ç¨ï¼åæå¨æ·»å å°selectedAPIs 213 selectedAPIs.push('dbzy'); 214 localStorage.setItem('selectedAPIs', JSON.stringify(selectedAPIs)); 215 216 // æ´æ°éä¸API计æ°ï¼å¦ææè¿ä¸ªå ç´ ï¼ 217 const countEl = document.getElementById('selectedAPICount'); 218 if (countEl) { 219 countEl.textContent = selectedAPIs.length; 220 } 221 } 222 223 showToast('å·²èªå¨éæ©è±ç£èµæºAPI', 'info'); 224 } 225 } 226 227 // å¡«å æç´¢æ¡å¹¶æ§è¡æç´¢ 228 const input = document.getElementById('searchInput'); 229 if (input) { 230 input.value = safeTitle; 231 await search(); // 使ç¨å·²æçsearch彿°æ§è¡æç´¢ 232 233 // æ´æ°æµè§å¨URLï¼ä½¿å ¶åæ å½åçæç´¢ç¶æ 234 try { 235 // 使ç¨URIç¼ç ç¡®ä¿ç¹æ®å符è½å¤æ£ç¡®æ¾ç¤º 236 const encodedQuery = encodeURIComponent(safeTitle); 237 // 使ç¨HTML5 History APIæ´æ°URLï¼ä¸å·æ°é¡µé¢ 238 window.history.pushState( 239 { search: safeTitle }, 240 `æç´¢: ${safeTitle} - LibreTV`, 241 `/s=${encodedQuery}` 242 ); 243 // æ´æ°é¡µé¢æ é¢ 244 document.title = `æç´¢: ${safeTitle} - LibreTV`; 245 } catch (e) { 246 console.error('æ´æ°æµè§å¨åå²å¤±è´¥:', e); 247 } 248 249 if (window.innerWidth <= 768) { 250 window.scrollTo({ 251 top: 0, 252 behavior: 'smooth' 253 }); 254 } 255 } 256} 257 258// 渲æçµå½±/çµè§å§åæ¢å¨ 259function renderDoubanMovieTvSwitch() { 260 // è·å忢æé®å ç´ 261 const movieToggle = document.getElementById('douban-movie-toggle'); 262 const tvToggle = document.getElementById('douban-tv-toggle'); 263 264 if (!movieToggle ||!tvToggle) return; 265 266 movieToggle.addEventListener('click', function() { 267 if (doubanMovieTvCurrentSwitch !== 'movie') { 268 // æ´æ°æé®æ ·å¼ 269 movieToggle.classList.add('bg-pink-600', 'text-white'); 270 movieToggle.classList.remove('text-gray-300'); 271 272 tvToggle.classList.remove('bg-pink-600', 'text-white'); 273 tvToggle.classList.add('text-gray-300'); 274 275 doubanMovieTvCurrentSwitch = 'movie'; 276 doubanCurrentTag = 'çé¨'; 277 278 // éæ°å è½½è±ç£å 容 279 renderDoubanTags(movieTags); 280 281 // æ¢ä¸æ¹æé®äºä»¶çå¬ 282 setupDoubanRefreshBtn(); 283 284 // åå§å è½½çé¨å 容 285 if (localStorage.getItem('doubanEnabled') === 'true') { 286 renderRecommend(doubanCurrentTag, doubanPageSize, doubanPageStart); 287 } 288 } 289 }); 290 291 // çµè§å§æé®ç¹å»äºä»¶ 292 tvToggle.addEventListener('click', function() { 293 if (doubanMovieTvCurrentSwitch !== 'tv') { 294 // æ´æ°æé®æ ·å¼ 295 tvToggle.classList.add('bg-pink-600', 'text-white'); 296 tvToggle.classList.remove('text-gray-300'); 297 298 movieToggle.classList.remove('bg-pink-600', 'text-white'); 299 movieToggle.classList.add('text-gray-300'); 300 301 doubanMovieTvCurrentSwitch = 'tv'; 302 doubanCurrentTag = 'çé¨'; 303 304 // éæ°å è½½è±ç£å 容 305 renderDoubanTags(tvTags); 306 307 // æ¢ä¸æ¹æé®äºä»¶çå¬ 308 setupDoubanRefreshBtn(); 309 310 // åå§å è½½çé¨å 容 311 if (localStorage.getItem('doubanEnabled') === 'true') { 312 renderRecommend(doubanCurrentTag, doubanPageSize, doubanPageStart); 313 } 314 } 315 }); 316} 317 318// 渲æè±ç£æ ç¾éæ©å¨ 319function renderDoubanTags(tags) { 320 const tagContainer = document.getElementById('douban-tags'); 321 if (!tagContainer) return; 322 323 // ç¡®å®å½ååºè¯¥ä½¿ç¨çæ ç¾å表 324 const currentTags = doubanMovieTvCurrentSwitch === 'movie' ? movieTags : tvTags; 325 326 // æ¸ ç©ºæ ç¾å®¹å¨ 327 tagContainer.innerHTML = ''; 328 329 // å æ·»å æ ç¾ç®¡çæé® 330 const manageBtn = document.createElement('button'); 331 manageBtn.className = 'py-1.5 px-3.5 rounded text-sm font-medium transition-all duration-300 bg-[#1a1a1a] text-gray-300 hover:bg-pink-700 hover:text-white border border-[#333] hover:border-white'; 332 manageBtn.innerHTML = '<span class="flex items-center"><svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
332<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path></svg>ç®¡çæ ç¾</span>'; 333 manageBtn.onclick = function() { 334 showTagManageModal(); 335 }; 336 tagContainer.appendChild(manageBtn); 337 338 // æ·»å æææ ç¾ 339 currentTags.forEach(tag => { 340 const btn = document.createElement('button'); 341 342 // è®¾ç½®æ ·å¼ 343 let btnClass = 'py-1.5 px-3.5 rounded text-sm font-medium transition-all duration-300 border '; 344 345 // å½åéä¸çæ ç¾ä½¿ç¨é«äº®æ ·å¼ 346 if (tag === doubanCurrentTag) { 347 btnClass += 'bg-pink-600 text-white shadow-md border-white'; 348 } else { 349 btnClass += 'bg-[#1a1a1a] text-gray-300 hover:bg-pink-700 hover:text-white border-[#333] hover:border-white'; 350 } 351 352 btn.className = btnClass; 353 btn.textContent = tag; 354 355 btn.onclick = function() { 356 if (doubanCurrentTag !== tag) { 357 doubanCurrentTag = tag; 358 doubanPageStart = 0; 359 renderRecommend(doubanCurrentTag, doubanPageSize, doubanPageStart); 360 renderDoubanTags(); 361 } 362 }; 363 364 tagContainer.appendChild(btn); 365 }); 366} 367 368// 设置æ¢ä¸æ¹æé®äºä»¶ 369function setupDoubanRefreshBtn() { 370 // ä¿®å¤IDï¼ä½¿ç¨æ£ç¡®çID douban-refresh è䏿¯ douban-refresh-btn 371 const btn = document.getElementById('douban-refresh'); 372 if (!btn) return; 373 374 btn.onclick = function() { 375 doubanPageStart += doubanPageSize; 376 if (doubanPageStart > 9 * doubanPageSize) { 377 doubanPageStart = 0; 378 } 379 380 renderRecommend(doubanCurrentTag, doubanPageSize, doubanPageStart); 381 }; 382} 383 384function fetchDoubanTags() { 385 const movieTagsTarget = `https://movie.douban.com/j/search_tags?type=movie` 386 fetchDoubanData(movieTagsTarget) 387 .then(data => { 388 movieTags = data.tags; 389 if (doubanMovieTvCurrentSwitch === 'movie') { 390 renderDoubanTags(movieTags); 391 } 392 }) 393 .catch(error => { 394 console.error("è·åè±ç£çé¨çµå½±æ ç¾å¤±è´¥ï¼", error); 395 }); 396 const tvTagsTarget = `https://movie.douban.com/j/search_tags?type=tv` 397 fetchDoubanData(tvTagsTarget) 398 .then(data => { 399 tvTags = data.tags; 400 if (doubanMovieTvCurrentSwitch === 'tv') { 401 renderDoubanTags(tvTags); 402 } 403 }) 404 .catch(error => { 405 console.error("è·åè±ç£çé¨çµè§å§æ ç¾å¤±è´¥ï¼", error); 406 }); 407} 408 409// 渲æç鍿¨èå 容 410function renderRecommend(tag, pageLimit, pageStart) { 411 const container = document.getElementById("douban-results"); 412 if (!container) return; 413 414 const loadingOverlayHTML = ` 415 <div class="absolute inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center z-10"> 416 <div class="flex items-center justify-center"> 417 <div class="w-6 h-6 border-2 border-pink-500 border-t-transparent rounded-full animate-spin inline-block"></div> 418 <span class="text-pink-500 ml-4">å è½½ä¸...</span> 419 </div> 420 </div> 421 `; 422 423 container.classList.add("relative"); 424 container.insertAdjacentHTML('beforeend', loadingOverlayHTML); 425 426 const target = `https://movie.douban.com/j/search_subjects?type=${doubanMovieTvCurrentSwitch}&tag=${tag}&sort=recommend&page_limit=${pageLimit}&page_start=${pageStart}`; 427 428 // 使ç¨éç¨è¯·æ±å½æ° 429 fetchDoubanData(target) 430 .then(data => { 431 renderDoubanCards(data, container); 432 }) 433 .catch(error => { 434 console.error("è·åè±ç£æ°æ®å¤±è´¥ï¼", error); 435 container.innerHTML = ` 436 <div class="col-span-full text-center py-8"> 437 <div class="text-red-400">â è·åè±ç£æ°æ®å¤±è´¥ï¼è¯·ç¨åéè¯</div> 438 <div class="text-gray-500 text-sm mt-2">æç¤ºï¼ä½¿ç¨VPNå¯è½æå©äºè§£å³æ¤é®é¢</div> 439 </div> 440 `; 441 }); 442} 443 444async function fetchDoubanData(url) { 445 // æ·»å è¶ æ¶æ§å¶ 446 const controller = new AbortController(); 447 const timeoutId = setTimeout(() => controller.abort(), 10000); // 10ç§è¶ æ¶ 448 449 // 设置请æ±é项ï¼å æ¬ä¿¡å·åå¤´é¨ 450 const fetchOptions = { 451 signal: controller.signal, 452 headers: { 453 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36', 454 'Referer': 'https://movie.douban.com/', 455 'Accept': 'application/json, text/plain, */*', 456 } 457 }; 458 459 try { 460 // å°è¯ç´æ¥è®¿é®ï¼è±ç£APIå¯è½å 许é¨åCORS请æ±ï¼ 461 const response = await fetch(PROXY_URL + encodeURIComponent(url), fetchOptions); 462 clearTimeout(timeoutId); 463 464 if (!response.ok) { 465 throw new Error(`HTTP error! Status: ${response.status}`); 466 } 467 468 return await response.json(); 469 } catch (err) { 470 console.error("è±ç£ API 请æ±å¤±è´¥ï¼ç´æ¥ä»£çï¼ï¼", err); 471 472 // 失败åå°è¯å¤ç¨æ¹æ³ï¼ä½ä¸ºå¤é 473 const fallbackUrl = `https://api.allorigins.win/get?url=${encodeURIComponent(url)}`; 474 475 try { 476 const fallbackResponse = await fetch(fallbackUrl); 477 478 if (!fallbackResponse.ok) { 479 throw new Error(`å¤ç¨API请æ±å¤±è´¥! ç¶æ: ${fallbackResponse.status}`); 480 } 481 482 const data = await fallbackResponse.json(); 483 484 // è§£æåå§å 容 485 if (data && data.contents) { 486 return JSON.parse(data.contents); 487 } else { 488 throw new Error("æ æ³è·åæææ°æ®"); 489 } 490 } catch (fallbackErr) { 491 console.error("è±ç£ API å¤ç¨è¯·æ±ä¹å¤±è´¥ï¼", fallbackErr); 492 throw fallbackErr; // å䏿åºé误ï¼è®©è°ç¨è å¤ç 493 } 494 } 495} 496 497// æ½å渲æè±ç£å¡ççé»è¾å°åç¬å½æ° 498function renderDoubanCards(data, container) { 499 // åå»ºææ¡£ç段以æé«æ§è½ 500 const fragment = document.createDocumentFragment(); 501 502 // å¦ææ²¡ææ°æ® 503 if (!data.subjects || data.subjects.length === 0) { 504 const emptyEl = document.createElement("div"); 505 emptyEl.className = "col-span-full text-center py-8"; 506 emptyEl.innerHTML = ` 507 <div class="text-pink-500">â ææ æ°æ®ï¼è¯·å°è¯å ¶ä»åç±»æå·æ°</div> 508 `; 509 fragment.appendChild(emptyEl); 510 } else { 511 // 循ç¯å建æ¯ä¸ªå½±è§å¡ç
512 data.subjects.forEach(item => { 513 const card = document.createElement("div"); 514 card.className = "bg-[#111] hover:bg-[#222] transition-all duration-300 rounded-lg overflow-hidden flex flex-col transform hover:scale-105 shadow-md hover:shadow-lg"; 515 516 // çæå¡çå 容ï¼ç¡®ä¿å®å ¨æ¾ç¤ºï¼é²æ¢XSSï¼ 517 const safeTitle = item.title 518 .replace(/</g, '<') 519 .replace(/>/g, '>') 520 .replace(/"/g, '"'); 521 522 const safeRate = (item.rate || "ææ ") 523 .replace(/</g, '<') 524 .replace(/>/g, '>'); 525 526 // å¤çå¾çURL 527 // 1. ç´æ¥ä½¿ç¨è±ç£å¾çURL (æ·»å no-referrer屿§) 528 const originalCoverUrl = item.cover; 529 530 // 2. ä¹åå¤ä»£çURLä½ä¸ºå¤é 531 const proxiedCoverUrl = PROXY_URL + encodeURIComponent(originalCoverUrl); 532 533 // 为ä¸å设å¤ä¼åå¡çå¸å± 534 card.innerHTML = ` 535 <div class="relative w-full aspect-[2/3] overflow-hidden cursor-pointer" onclick="fillAndSearchWithDouban('${safeTitle}')"> 536 <img src="${originalCoverUrl}" alt="${safeTitle}" 537 class="w-full h-full object-cover transition-transform duration-500 hover:scale-110" 538 onerror="this.onerror=null; this.src='${proxiedCoverUrl}'; this.classList.add('object-contain');" 539 loading="lazy" referrerpolicy="no-referrer"> 540 <div class="absolute inset-0 bg-gradient-to-t from-black to-transparent opacity-60"></div> 541 <div class="absolute bottom-2 left-2 bg-black/70 text-white text-xs px-2 py-1 rounded-sm"> 542 <span class="text-yellow-400">â </span> ${safeRate} 543 </div> 544 <div class="absolute bottom-2 right-2 bg-black/70 text-white text-xs px-2 py-1 rounded-sm hover:bg-[#333] transition-colors"> 545 <a href="${item.url}" target="_blank" rel="noopener noreferrer" title="å¨è±ç£æ¥ç" onclick="event.stopPropagation();"> 546 ð 547 </a> 548 </div> 549 </div> 550 <div class="p-2 text-center bg-[#111]"> 551 <button onclick="fillAndSearchWithDouban('${safeTitle}')" 552 class="text-sm font-medium text-white truncate w-full hover:text-pink-400 transition" 553 title="${safeTitle}"> 554 ${safeTitle} 555 </button> 556 </div> 557 `; 558 559 fragment.appendChild(card); 560 }); 561 } 562 563 // æ¸ ç©ºå¹¶æ·»å æææ°å ç´ 564 container.innerHTML = ""; 565 container.appendChild(fragment); 566} 567 568// éç½®å°é¦é¡µ 569function resetToHome() { 570 resetSearchArea(); 571 updateDoubanVisibility(); 572} 573 574// å è½½è±ç£é¦é¡µå 容 575document.addEventListener('DOMContentLoaded', initDouban); 576 577// æ¾ç¤ºæ ç¾ç®¡çæ¨¡ææ¡ 578function showTagManageModal() { 579 // ç¡®ä¿æ¨¡ææ¡å¨é¡µé¢ä¸åªæä¸ä¸ªå®ä¾ 580 let modal = document.getElementById('tagManageModal'); 581 if (modal) { 582 document.body.removeChild(modal); 583 } 584 585 // åå»ºæ¨¡ææ¡å ç´ 586 modal = document.createElement('div'); 587 modal.id = 'tagManageModal'; 588 modal.className = 'fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-40'; 589 590 // å½å使ç¨çæ ç¾ç±»ååé»è®¤æ ç¾ 591 const isMovie = doubanMovieTvCurrentSwitch === 'movie'; 592 const currentTags = isMovie ? movieTags : tvTags; 593 const defaultTags = isMovie ? defaultMovieTags : defaultTvTags; 594 595 // æ¨¡ææ¡å 容 596 modal.innerHTML = ` 597 <div class="bg-[#191919] rounded-lg p-6 max-w-md w-full max-h-[90vh] overflow-y-auto relative"> 598 <button id="closeTagModal" class="absolute top-4 right-4 text-gray-400 hover:text-white text-xl">×</button> 599 600 <h3 class="text-xl font-bold text-white mb-4">æ ç¾ç®¡ç (${isMovie ? 'çµå½±' : 'çµè§å§'})</h3> 601 602 <div class="mb-4"> 603 <div class="flex justify-between items-center mb-2"> 604 <h4 class="text-lg font-medium text-gray-300">æ ç¾å表</h4> 605 <button id="resetTagsBtn" class="text-xs px-2 py-1 bg-gray-700 hover:bg-gray-600 text-white rounded"> 606 æ¢å¤é»è®¤æ ç¾ 607 </button> 608 </div> 609 <div class="grid grid-cols-2 sm:grid-cols-3 gap-2 mb-4" id="tagsGrid"> 610 ${currentTags.length ? currentTags.map(tag => { 611 // "çé¨"æ ç¾ä¸è½å é¤ 612 const canDelete = tag !== 'çé¨'; 613 return ` 614 <div class="bg-[#1a1a1a] text-gray-300 py-1.5 px-3 rounded text-sm font-medium flex justify-between items-center group">
615 <span>${tag}</span> 616 ${canDelete ? 617 `<button class="delete-tag-btn text-gray-500 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity" 618 data-tag="${tag}">â</button>` : 619 `<span class="text-gray-500 text-xs italic opacity-0 group-hover:opacity-100">å¿ é</span>` 620 } 621 </div> 622 `; 623 }).join('') : 624 `<div class="col-span-full text-center py-4 text-gray-500">æ æ ç¾ï¼è¯·æ·»å ææ¢å¤é»è®¤</div>`} 625 </div> 626 </div> 627 628 <div class="border-t border-gray-700 pt-4"> 629 <h4 class="text-lg font-medium text-gray-300 mb-3">æ·»å æ°æ ç¾</h4> 630 <form id="addTagForm" class="flex items-center"> 631 <input type="text" id="newTagInput" placeholder="è¾å ¥æ ç¾åç§°..." 632 class="flex-1 bg-[#222] text-white border border-gray-700 rounded px-3 py-2 focus:outline-none focus:border-pink-500"> 633 <button type="submit" class="ml-2 bg-pink-600 hover:bg-pink-700 text-white px-4 py-2 rounded">æ·»å </button> 634 </form> 635 <p class="text-xs text-gray-500 mt-2">æç¤ºï¼æ ç¾åç§°ä¸è½ä¸ºç©ºï¼ä¸è½éå¤ï¼ä¸è½å å«ç¹æ®å符</p> 636 </div> 637 </div> 638 `; 639 640 // æ·»å æ¨¡ææ¡å°é¡µé¢ 641 document.body.appendChild(modal); 642 643 // ç¦ç¹æ¾å¨è¾å ¥æ¡ä¸ 644 setTimeout(() => { 645 document.getElementById('newTagInput').focus(); 646 }, 100); 647 648 // æ·»å äºä»¶çå¬å¨ - å ³éæé® 649 document.getElementById('closeTagModal').addEventListener('click', function() { 650 document.body.removeChild(modal); 651 }); 652 653 // æ·»å äºä»¶çå¬å¨ - ç¹å»æ¨¡ææ¡å¤é¨å ³é 654 modal.addEventListener('click', function(e) { 655 if (e.target === modal) { 656 document.body.removeChild(modal); 657 } 658 }); 659 660 // æ·»å äºä»¶çå¬å¨ - æ¢å¤é»è®¤æ ç¾æé® 661 document.getElementById('resetTagsBtn').addEventListener('click', function() { 662 resetTagsToDefault(); 663 showTagManageModal(); // éæ°å è½½æ¨¡ææ¡ 664 }); 665 666 // æ·»å äºä»¶çå¬å¨ - å 餿 ç¾æé® 667 const deleteButtons = document.querySelectorAll('.delete-tag-btn'); 668 deleteButtons.forEach(btn => { 669 btn.addEventListener('click', function() { 670 const tagToDelete = this.getAttribute('data-tag'); 671 deleteTag(tagToDelete); 672 showTagManageModal(); // éæ°å è½½æ¨¡ææ¡ 673 }); 674 }); 675 676 // æ·»å äºä»¶çå¬å¨ - 表åæäº¤ 677 document.getElementById('addTagForm').addEventListener('submit', function(e) { 678 e.preventDefault(); 679 const input = document.getElementById('newTagInput'); 680 const newTag = input.value.trim(); 681 682 if (newTag) { 683 addTag(newTag); 684 input.value = ''; 685 showTagManageModal(); // éæ°å è½½æ¨¡ææ¡ 686 } 687 }); 688} 689 690// æ·»å æ ç¾ 691function addTag(tag) { 692 // å®å ¨å¤çæ ç¾åï¼é²æ¢XSS 693 const safeTag = tag 694 .replace(/</g, '<') 695 .replace(/>/g, '>') 696 .replace(/"/g, '"'); 697 698 // ç¡®å®å½å使ç¨çæ¯çµå½±è¿æ¯çµè§å§æ ç¾ 699 const isMovie = doubanMovieTvCurrentSwitch === 'movie'; 700 const currentTags = isMovie ? movieTags : tvTags; 701 702 // æ£æ¥æ¯å¦å·²åå¨ï¼å¿½ç¥å¤§å°åï¼ 703 const exists = currentTags.some( 704 existingTag => existingTag.toLowerCase() === safeTag.toLowerCase() 705 ); 706 707 if (exists) { 708 showToast('æ ç¾å·²åå¨', 'warning'); 709 return; 710 } 711 712 // æ·»å å°å¯¹åºçæ ç¾æ°ç» 713 if (isMovie) { 714 movieTags.push(safeTag); 715 } else { 716 tvTags.push(safeTag); 717 } 718 719 // ä¿åå°æ¬å°åå¨ 720 saveUserTags(); 721 722 // éæ°æ¸²ææ ç¾ 723 renderDoubanTags(); 724 725 showToast('æ ç¾æ·»å æå', 'success'); 726} 727 728// å 餿 ç¾ 729function deleteTag(tag) { 730 // ç鍿 ç¾ä¸è½å é¤ 731 if (tag === 'çé¨') { 732 showToast('ç鍿 ç¾ä¸è½å é¤', 'warning'); 733 return; 734 } 735 736 // ç¡®å®å½å使ç¨çæ¯çµå½±è¿æ¯çµè§å§æ ç¾ 737 const isMovie = doubanMovieTvCurrentSwitch === 'movie'; 738 const currentTags = isMovie ? movieTags : tvTags; 739 740 // å¯»æ¾æ ç¾ç´¢å¼ 741 const index = currentTags.indexOf(tag); 742 743 // 妿æ¾å°æ ç¾ï¼åå é¤ 744 if (index !== -1) { 745 currentTags.splice(index, 1); 746 747 // ä¿åå°æ¬å°åå¨ 748 saveUserTags(); 749
750 // 妿å½åéä¸çæ¯è¢«å é¤çæ ç¾ï¼åé置为"çé¨" 751 if (doubanCurrentTag === tag) { 752 doubanCurrentTag = 'çé¨'; 753 doubanPageStart = 0; 754 renderRecommend(doubanCurrentTag, doubanPageSize, doubanPageStart); 755 } 756 757 // éæ°æ¸²ææ ç¾ 758 renderDoubanTags(); 759 760 showToast('æ ç¾å 餿å', 'success'); 761 } 762} 763 764// é置为é»è®¤æ ç¾ 765function resetTagsToDefault() { 766 // ç¡®å®å½å使ç¨çæ¯çµå½±è¿æ¯çµè§å§ 767 const isMovie = doubanMovieTvCurrentSwitch === 'movie'; 768 769 // é置为é»è®¤æ ç¾ 770 if (isMovie) { 771 movieTags = [...defaultMovieTags]; 772 } else { 773 tvTags = [...defaultTvTags]; 774 } 775 776 // 设置å½åæ ç¾ä¸ºçé¨ 777 doubanCurrentTag = 'çé¨'; 778 doubanPageStart = 0; 779 780 // ä¿åå°æ¬å°åå¨ 781 saveUserTags(); 782 783 // éæ°æ¸²ææ ç¾åå 容 784 renderDoubanTags(); 785 renderRecommend(doubanCurrentTag, doubanPageSize, doubanPageStart); 786 787 showToast('å·²æ¢å¤é»è®¤æ ç¾', 'success'); 788}
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.