// ===== Anime keywords for categorization ===== const ANIME_KWS=['仙逆','凡人修仙传','斗破苍穹','斗破','盘龙','完美世界','一念永恒','妖神记','星辰变','遮天','神墓','吞噬星空','武动乾坤','大主宰','全职高手','鬼灭之刃','海贼王','火影忍者','死神','龙珠','进击的巨人','咒术回战','一人之下','狐妖小红娘','魔道祖师','天官赐福','时光代理人','大王饶命','斗罗大陆','绝世唐门','不良人','秦时明月','全职法师','牧神记','三体','灵笼','雾山五行','凡人','仙王的日常生活','百妖谱','眷思量','镖人','伍六七','刺客伍六七','葬送的芙莉莲','间谍过家家'] // ===== Quotes ===== const QUOTES=['学而时习之,不亦说乎。','温故而知新,可以为师矣。','三人行,必有我师焉。','学而不思则罔,思而不学则殆。','博学之,审问之,慎思之,明辨之,笃行之。','千里之行,始于足下。','不积跬步,无以至千里。','知之为知之,不知为不知,是知也。','工欲善其事,必先利其器。','玉不琢,不成器;人不学,不知道。','学以致用,知行合一。','学海无涯,勤作舟。','书山有路,勤为径。','宝剑锋从磨砺出,梅花香自苦寒来。','锲而不舍,金石可镂。','业精于勤,荒于嬉。','读书破万卷,下笔如有神。','路漫漫其修远兮,吾将上下而求索。','采菊东篱下,悠然见南山。','海内存知己,天涯若比邻。','长风破浪会有时,直挂云帆济沧海。','会当凌绝顶,一览众山小。','山重水复疑无路,柳暗花明又一村。'] // ===== Home Page ===== function homeSearch(){ const q=document.getElementById('homeSearchInput').value.trim() if(q)doSearchFromHome(q) } function doSearchFromHome(q){ document.getElementById('homePage').style.display='none' document.getElementById('searchView').style.display='block' document.getElementById('searchInput').value=q window.history.replaceState({},'','/h5?q='+encodeURIComponent(q)) doSearch() } function renderHomePage(data){ fetch("/api/site-config").then(r=>r.json()).then(cfg=>{ // 显示 Logo(优先图片,其次文字) var logoEl=document.getElementById("homeLogo"); var headerEl=document.getElementById("headerTitle"); if(cfg.site_logo){ logoEl.innerHTML='logo'; logoEl.style.display=""; headerEl.innerHTML='logo'; headerEl.style.display=""; }else if(cfg.site_name){ logoEl.textContent=cfg.site_name; logoEl.style.display=""; headerEl.textContent=cfg.site_name; headerEl.style.display=""; }else{ logoEl.textContent="CloudSearch"; logoEl.style.display=""; headerEl.textContent="CloudSearch"; headerEl.style.display=""; } if(cfg.site_disclaimer){ document.getElementById("footerContent").innerHTML=cfg.site_disclaimer.replace(/\n/g,'
'); document.getElementById("siteFooter").style.display="block"; } }).catch(()=>{}) const categories=data.categories||[] const fetchedAt=data.fetchedAt||'' // Quote fetch('https://v1.hitokoto.cn/').then(r=>r.json()).then(d=>{ document.getElementById('homeQuote').textContent='「 '+d.hitokoto+' 」' document.getElementById('homeQuoteAuthor').textContent='---'+(d.from_who||d.from||'') }).catch(()=>{ document.getElementById('homeQuote').textContent='「 学而时习之,不亦说乎。 」' document.getElementById('homeQuoteAuthor').textContent='---孔子' }) // Store expanded state per category window.__expanded=window.__expanded||{} window.__activeTab=window.__activeTab||{} const el=document.getElementById('homeRankings') let html='' for(const cat of categories){ const icons={movie:'🎬',tv:'📺',western_movie:'🎥',western:'🌍',donghua:'🐉',global_anime:'🌐',variety:'🎤',niche:'💎',hotsite:'🏆'} const icon=icons[cat.category]||'📋' const key=cat.category if(!window.__activeTab[key])window.__activeTab[key]='hot' html+='
' html+='
'+ ''+icon+' '+cat.label+''+ '
'+ '热榜'+ '最新'+ '
'+ '
' html+='
' const items=window.__activeTab[key]==='hot'?(cat.hot||[]):(cat.newest||[]) html+=renderRankItems(items,key,false) html+='
' // 数据来源 html+='
'+ ''+(cat.category!=='hotsite'?'数据来源:TMDB':'本站搜索数据')+''+ ''+fetchedAt+''+ '
' } el.innerHTML=html } function renderRankItems(items,key,expanded){ if(!items||items.length===0)return'
暂无数据
' const limit=3 const show=expanded?items.length:Math.min(limit,items.length) let html=items.slice(0,show).map((item,i)=>{ const c=i<3?' rank-idx top3':' rank-idx' return '
'+ ''+(i+1)+''+ ''+item.keyword+''+ ''+(item.rating?'⭐'+item.rating:item.searchCount)+''+ '
' }).join('') if(items.length>limit&&!expanded){ html+='
展开全部 ▼
' } return html } function expandRank(key){ const container=document.getElementById('ritems-'+key) if(!container)return const tab=window.__activeTab[key]||'hot' const data=JSON.parse(tab==='hot'?container.dataset.hot:container.dataset.newest) container.innerHTML=renderRankItems(data.items,key,true) } function switchRankTab(category,tab){ window.__activeTab[category]=tab const tabsContainer=document.getElementById('rtabs-'+category) if(tabsContainer){ tabsContainer.querySelectorAll('.rank-tab').forEach(t=>t.className='rank-tab') tabsContainer.querySelector(tab==='hot'?'.rank-tab:first-child':'.rank-tab:last-child').className='rank-tab active' } const container=document.getElementById('ritems-'+category) if(container){ const data=JSON.parse(tab==='hot'?container.dataset.hot:container.dataset.newest) container.innerHTML=renderRankItems(data.items,category,false) } } let userInfo = null let allResults = [] let allChannels = [] let activeTab = '' let currentSaveItem = null const CLOUD_ICONS = {quark:'☁️',baidu:'🔵',aliyun:'🟠','115':'🟣',tianyi:'🔷','123pan':'🔴',uc:'🟡',xunlei:'🟢',pikpak:'🟤',magnet:'🧲',ed2k:'🔗',others:'📁'} const CLOUD_LABELS = {quark:'夸克网盘',baidu:'百度网盘',aliyun:'阿里云盘','115':'115网盘',tianyi:'天翼云盘','123pan':'123云盘',uc:'UC网盘',xunlei:'迅雷云盘',pikpak:'PikPak',magnet:'磁力链接',ed2k:'电驴链接',others:'其他'} const CLOUD_COLORS = {quark:'#07c160',baidu:'#4e6ef2',aliyun:'#ff6a00','115':'#9b59b6',tianyi:'#00a1d6','123pan':'#e74c3c',uc:'#f39c12',xunlei:'#2ecc71',pikpak:'#8e44ad',magnet:'#95a5a6',ed2k:'#7f8c8d',others:'#95a5a6'} const CLOUD_ORDER = {quark:1,baidu:2,aliyun:3,'115':4,tianyi:5,'123pan':6,uc:7,xunlei:8,pikpak:9,magnet:10,ed2k:11,others:12} // ===== Fetch helpers ===== function getToken(){return localStorage.getItem('h5_admin_token')} function apiHeaders(){const h={'Content-Type':'application/json'};const t=getToken();if(t)h['Authorization']='Bearer '+t;return h} // ===== Toast ===== let toastTimer function showToast(msg,isError){ const el=document.getElementById('toast') el.textContent=msg el.className='toast show'+(isError?' error':'') clearTimeout(toastTimer) toastTimer=setTimeout(()=>el.className='toast',2000) } // ===== User ===== async function checkLogin(){ try{ const res=await fetch('/api/me',{headers:apiHeaders()}) if(res.ok){ const data=await res.json() if(data.loggedIn){ userInfo=data document.getElementById('userArea').innerHTML=''+data.username+'' } } }catch(e){} } function logout(){ localStorage.removeItem('h5_admin_token') userInfo=null document.getElementById('userArea').innerHTML='' showToast('已退出') } function showLogin(){ document.getElementById('loginErr').textContent='' document.getElementById('loginUser').value='' document.getElementById('loginPass').value='' document.getElementById('loginModal').style.display='block' document.getElementById('overlay').style.display='block' } function closeLogin(){ document.getElementById('loginModal').style.display='none' document.getElementById('overlay').style.display='none' } async function handleLogin(){ const user=document.getElementById('loginUser').value.trim() const pass=document.getElementById('loginPass').value if(!user||!pass){showToast('请输入用户名和密码',true);return} const btn=document.getElementById('loginBtn') btn.disabled=true;btn.textContent='登录中...' try{ const res=await fetch('/api/admin/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:user,password:pass})}) if(res.ok){ const data=await res.json() localStorage.setItem('h5_admin_token',data.token) userInfo={username:user} document.getElementById('userArea').innerHTML=''+user+'' closeLogin() showToast('登录成功') }else{ const err=await res.json().catch(()=>({})) document.getElementById('loginErr').textContent=err.error||'登录失败' } }catch(e){ document.getElementById('loginErr').textContent='网络错误' }finally{ btn.disabled=false;btn.textContent='登录' } } // ===== Search ===== function handleKeydown(e){if(e.key==='Enter')doSearch()} let searchTimer function doSearch(){ const q=document.getElementById('searchInput').value.trim() if(!q)return // Update URL window.history.replaceState({},'','/h5?q='+encodeURIComponent(q)) // Show loading document.getElementById('results').innerHTML='' document.getElementById('tabs').style.display='none' document.getElementById('infoBar').style.display='none' document.getElementById('loading').style.display='block' document.getElementById('loadingText').textContent='🔍 正在搜索中...' document.getElementById('searchBtn').disabled=true let progress=0 const bar=document.getElementById('loadingBar') const progressTimer=setInterval(()=>{ if(progress<60)progress+=1+Math.random()*3 else if(progress<85)progress+=0.5+Math.random() bar.style.width=progress+'%' },200) // Use streaming search for live updates streamSearch(q,progressTimer,bar) } async function streamSearch(q,progressTimer,bar){ const startTime=Date.now() try{ const response=await fetch('/api/query',{method:'POST',headers:apiHeaders(),body:JSON.stringify({q})}) if(!response.ok)throw new Error('搜索失败 ('+response.status+')') const reader=response.body.getReader() const decoder=new TextDecoder() let buffer='' let allItems=[] let channels=[] let totalCount=0 let filteredCount=0 while(true){ const {done,value}=await reader.read() if(done)break buffer+=decoder.decode(value,{stream:true}) const lines=buffer.split('\n') buffer=lines.pop()||'' for(const line of lines){ if(!line.trim())continue try{ const msg=JSON.parse(line) if(msg.type==='stats'){ totalCount=msg.total||0 filteredCount=msg.filtered||0 document.getElementById('loadingText').textContent='🔍 搜索到 '+totalCount+' 条,正在验证...' }else if(msg.type==='result'){ if(msg.valid&&msg.id){ allItems.push(msg.id) } }else if(msg.type==='complete'){ const results=msg.results||[] channels=msg.channels||[] clearInterval(progressTimer) bar.style.width='100%' setTimeout(()=>renderResults(results,channels,totalCount,filteredCount,Date.now()-startTime),300) return } }catch(e){} } } }catch(e){ clearInterval(progressTimer) document.getElementById('loading').style.display='none' document.getElementById('searchBtn').disabled=false document.getElementById('results').innerHTML='
搜索失败:'+e.message+'
' } } // ===== Render ===== function renderResults(results,channels,totalCount,filteredCount,time){ document.getElementById('loading').style.display='none' document.getElementById('searchBtn').disabled=false allResults=results allChannels=channels||[] // Info bar if(totalCount>0){ document.getElementById('infoBar').style.display='flex' document.getElementById('infoCount').textContent='已为您挑选到最符合 '+totalCount+' 条结果' document.getElementById('infoTime').textContent='⏱ '+time+'ms' if(filteredCount>0)document.getElementById('infoFiltered').textContent='❌ 失效 '+filteredCount else document.getElementById('infoFiltered').textContent='' }else{ document.getElementById('infoBar').style.display='none' } // Build tabs const tabsEl=document.getElementById('tabs') tabsEl.innerHTML='' const typeCounts={} for(const r of results){const ct=r.cloud_type||'others';typeCounts[ct]=(typeCounts[ct]||0)+1} const sorted=Object.keys(typeCounts).sort((a,b)=>(CLOUD_ORDER[a]||99)-(CLOUD_ORDER[b]||99)) // "全部" tab const allTab=document.createElement('div') allTab.className='tab active' allTab.textContent='📋 全部 ('+results.length+')' allTab.onclick=()=>{setActiveTab('');renderCardList(results)} tabsEl.appendChild(allTab) for(const ct of sorted){ const tab=document.createElement('div') tab.className='tab' tab.textContent=(CLOUD_ICONS[ct]||'📁')+' '+(CLOUD_LABELS[ct]||ct)+' ('+typeCounts[ct]+')' tab.onclick=()=>{setActiveTab(ct);renderCardList(results.filter(r=>(r.cloud_type||'others')===ct))} tabsEl.appendChild(tab) } tabsEl.style.display=results.length>0?'flex':'none' activeTab='' // Render cards renderCardList(results) } function setActiveTab(ct){ activeTab=ct document.querySelectorAll('.tab').forEach((t,i)=>{ const isAll=i===0&&!ct const active=i>0&&ct&&t.textContent.includes(CLOUD_LABELS[ct]) t.className='tab'+(active||isAll?' active':'') }) } function renderCardList(items){ const el=document.getElementById('results') if(items.length===0){ el.innerHTML='
暂无结果
' return } el.innerHTML=items.map((item,idx)=>{ const coverHtml=item.cover ? '' : '
'+escapeHtml(CLOUD_ICONS[item.cloud_type||'others'])+'
' const cloudLabel=CLOUD_LABELS[item.cloud_type]||item.cloud_type||'' const cloudColor=CLOUD_COLORS[item.cloud_type]||'#95a5a6' const tags=extractTags(item.title||'') const cleanTitle=(item.title||'').replace(/【[^】]+】/g,'').trim() const relativeTime=formatTime(item.update_time||item.datetime||'') return '
'+ '
'+coverHtml+''+cloudLabel+'
'+ '
'+ '
'+escapeHtml(cleanTitle)+'
'+ '
🕐 '+relativeTime+''+(item.file_size?'📦 '+escapeHtml(item.file_size)+'':'')+'
'+ (tags.length>0?'
'+tags.map(t=>''+escapeHtml(t)+'').join('')+'
':'')+ '
'+ ''+(item.source?escapeHtml(item.source):'网盘')+''+ ''+ '
'+ '
'+ '
' }).join('') // Store items for save reference window.__h5Results=items } function escapeHtml(s){if(!s)return '';return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"')} function extractTags(title){ const tags=[] // Quality tags const quality=['4K','1080P','2160P','720P','480P','HDR','HDR10','BluRay','REMUX','HEVC','x264','x265','WEB-DL','WEBRip'] for(const q of quality){if(title.includes(q)&&!tags.includes(q))tags.push(q)} const kw=['杜比视界','杜比全景声','高码率','内封简繁英字幕','内嵌字幕','中文字幕','中英字幕'] for(const k of kw){if(title.includes(k)&&!tags.includes(k))tags.push(k)} return tags.slice(0,6) } function isQualityTag(t){const q=['4K','1080P','2160P','720P','480P','HDR','HDR10','BluRay','REMUX','HEVC','x264','x265','臻彩','高清','WEB-DL','WEBRip'];return q.includes(t)} function formatTime(s){ if(!s)return '' const d=new Date(s) if(isNaN(d.getTime()))return s.slice(0,10) const diff=Date.now()-d.getTime() if(diff<0)return s.slice(0,10) const mins=Math.floor(diff/60000) if(mins<60)return mins<=1?'刚刚':mins+' 分钟前' const hours=Math.floor(mins/60) if(hours<24)return hours+' 小时前' const days=Math.floor(hours/24) if(days<30)return days+' 天前' return Math.floor(days/30)+' 个月前' } // ===== Save / Share ===== function saveItem(idx){ const items=window.__h5Results||[] currentSaveItem=items[idx] if(!currentSaveItem)return document.getElementById('progressSteps').style.display='block' document.getElementById('shareContent').style.display='none' document.getElementById('saveError').style.display='none' document.getElementById('copyBtn2').style.display='none' const title=(currentSaveItem.title||'').replace(/【[^】]+】/g,'').trim()||'资源' document.getElementById('shareTitle').textContent=title // Show modal document.getElementById('overlay').style.display='block' document.getElementById('shareModal').style.display='block' // Reset steps resetSteps() advanceStep(1) // Call save API doSave() } async function doSave(){ try{ const res=await fetch('/api/save',{method:'POST',headers:apiHeaders(),body:JSON.stringify({type:'search',source:currentSaveItem,target_cloud:currentSaveItem.cloud_type||'quark'})}) const data=await res.json() if(!data.success){ document.getElementById('progressSteps').style.display='none' document.getElementById('saveError').style.display='flex' document.getElementById('saveError').textContent=data.message||data.error||'保存失败' return } // Step 2 advanceStep(2) await sleep(500) // Step 3 advanceStep(3) await sleep(300) if(data.share_url){ advanceStep(4) await sleep(200) showShareResult(data) }else{ advanceStep(4) document.getElementById('progressSteps').style.display='none' document.getElementById('saveError').style.display='flex' document.getElementById('saveError').textContent='生成分享链接失败' } }catch(e){ document.getElementById('progressSteps').style.display='none' document.getElementById('saveError').style.display='flex' document.getElementById('saveError').textContent=e.message||'保存请求失败' } } function showShareResult(data){ document.getElementById('progressSteps').style.display='none' document.getElementById('shareContent').style.display='block' const link=data.share_url document.getElementById('shareLinkInput').value=link const diskLabel=CLOUD_LABELS[currentSaveItem.cloud_type]||'夸克网盘' document.getElementById('qrLabel').textContent=diskLabel+' APP扫码转存' // Generate QR const qrContainer=document.getElementById('qrContainer') qrContainer.innerHTML='' new QRCode(qrContainer,{text:link,width:140,height:140}) // Password const pwd=data.share_pwd||data.sharePwd||'' if(pwd){ document.getElementById('sharePwdRow').style.display='flex' document.getElementById('sharePwdTag').textContent=pwd }else{ document.getElementById('sharePwdRow').style.display='none' } document.getElementById('copyBtn2').style.display='inline-block' } function resetSteps(){ for(let i=1;i<=3;i++){ const el=document.getElementById('step'+i) el.className='step' el.querySelector('.step-dot').innerHTML=''+i+'' el.querySelector('.step-status').textContent='等待中' el.querySelector('.step-status').className='step-status wait' } } function advanceStep(n){ for(let i=1;i<=3;i++){ const el=document.getElementById('step'+i) if(i' const titles=['正在转存到','正在重命名文件(防和谐)','正在生成分享链接'] el.querySelector('.step-title').textContent=titles[i-1]+'...' el.querySelector('.step-status').textContent='进行中' el.querySelector('.step-status').className='step-status doing' } } } function sleep(ms){return new Promise(r=>setTimeout(r,ms))} function copyShareLink(){ const input=document.getElementById('shareLinkInput') if(!input.value)return if(navigator.clipboard&&navigator.clipboard.writeText){ navigator.clipboard.writeText(input.value).then(()=>showToast('链接已复制')).catch(()=>fallbackCopy(input.value)) }else{ fallbackCopy(input.value) } } function fallbackCopy(text){ const ta=document.createElement('textarea') ta.value=text;ta.style.position='fixed';ta.style.left='-9999px';document.body.appendChild(ta) ta.select() try{document.execCommand('copy');showToast('链接已复制')}catch{showToast('复制失败',true)} document.body.removeChild(ta) } function openDisclaimer(){ window.open('/disclaimer/','_blank') } function closeModal(){ document.getElementById('overlay').style.display='none' document.getElementById('shareModal').style.display='none' document.getElementById('loginModal').style.display='none' } // ===== Init ===== checkLogin() // Add Enter key handler for home search document.getElementById('homeSearchInput').addEventListener('keydown',function(e){if(e.key==='Enter')homeSearch()}) // Also add for search view input document.getElementById('searchInput').addEventListener('keydown',function(e){if(e.key==='Enter')doSearch()}) // Fetch home page data fetch('/api/rankings/categorized').then(r=>r.json()).then(data=>{ renderHomePage(data) }).catch(()=>{ document.getElementById('homeQuote').textContent='「 学而时习之,不亦说乎。 」' document.getElementById('homeQuoteAuthor').textContent='---孔子' }) // Check URL for query const params=new URLSearchParams(window.location.search) const q=params.get('q') if(q){ document.getElementById('homePage').style.display='none' document.getElementById('searchView').style.display='block' document.getElementById('searchInput').value=q doSearch() } // ===== Dark Mode Toggle ===== (function() { var btn = document.createElement('button'); btn.className = 'theme-btn'; btn.title = '切换暗色模式'; var isDark = localStorage.getItem('h5_theme') === 'dark'; if (!isDark && window.matchMedia('(prefers-color-scheme: dark)').matches) isDark = true; btn.textContent = isDark ? '☀️' : '🌙'; if (isDark) document.documentElement.setAttribute('data-theme', 'dark'); btn.onclick = function() { var dark = document.documentElement.getAttribute('data-theme') !== 'dark'; document.documentElement.setAttribute('data-theme', dark ? 'dark' : ''); localStorage.setItem('h5_theme', dark ? 'dark' : 'light'); btn.textContent = dark ? '☀️' : '🌙'; }; document.body.appendChild(btn); })();