// ===== 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='
';
logoEl.style.display="";
headerEl.innerHTML='';
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✓'
el.querySelector('.step-status').textContent='已完成'
el.querySelector('.step-status').className='step-status done'
}else if(i===n){
el.className='step active'
el.querySelector('.step-dot').innerHTML=''+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);
})();