Sync all projects
Этот коммит содержится в:
@@ -0,0 +1,7 @@
|
||||
<!doctypehtml><html lang=zh-CN><meta charset=UTF-8><meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"name=viewport><title>SupJav</title><style>*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}body,html{min-height:100%;background:0 0}html:not(.fm-native),html:not(.fm-native) body{background:#1a1a2e}body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;color:#e0e0e0}.header{height:52px;background:#16162a;display:flex;align-items:center;padding:0 12px;gap:10px;position:sticky;top:0;z-index:100}.logo{font-size:18px;font-weight:700;white-space:nowrap;color:#fff}.search-group{flex:1;display:flex;align-items:center;background:#2a2a4a;border-radius:25px;padding-right:4px}.search-group .search-box{flex:1;border:none;background:0 0;outline:0;padding:0 10px;height:34px;color:#e0e0e0;font-size:12px}.search-group .search-btn{width:32px;height:28px;background:#e0592f;border:none;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer}.search-group .search-btn svg{width:14px;height:14px;color:#fff}.category-bar{display:flex;gap:8px;padding:8px 12px;overflow-x:auto;background:#16162a}.category-bar::-webkit-scrollbar{display:none}.category-item{padding:6px 16px;border-radius:20px;background:rgba(42,42,74,.6);font-size:13px;white-space:nowrap;color:#e0e0e0;cursor:pointer;transition:all .2s}.category-item.active{background:#e0592f;color:#fff}.list-section{background:rgba(26,26,46,.5);padding:12px}.list-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}.item{display:flex;flex-direction:column;cursor:pointer;min-width:0}.item-img-wrap{position:relative}.item-img{width:100%;aspect-ratio:3/4;background:#16162a;border-radius:8px 8px 0 0;object-fit:cover;display:block}.img-year{position:absolute;top:6px;left:6px;background:#e0592f;color:#fff;font-size:10px;padding:2px 6px;border-radius:4px}.img-remark{position:absolute;bottom:6px;right:6px;background:rgba(0,0,0,.7);color:#fff;font-size:10px;padding:2px 6px;border-radius:4px}.item-title{padding:6px 8px;font-size:12px;text-align:center;background:#16162a;border-radius:0 0 8px 8px;line-height:1.3;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.loading{text-align:center;padding:20px;color:rgba(224,224,224,.5);font-size:12px}.error-tip{text-align:center;padding:40px;color:#e0592f;font-size:14px}.load-more,.no-more{text-align:center;padding:20px;color:rgba(224,224,224,.5);font-size:12px;width:100%}.search-overlay{position:fixed;inset:0;background:#1a1a2e;z-index:150;display:none;overflow-y:auto}.search-overlay.active{display:block}.search-nav{height:52px;background:#16162a;display:flex;align-items:center;justify-content:space-between;padding:0 16px;position:sticky;top:0;z-index:10}.search-title{font-size:16px;font-weight:700;color:#fff}.detail-overlay{position:fixed;inset:0;background:rgba(0,0,0,.95);z-index:200;display:none;overflow-y:auto}.detail-overlay.active{display:block}.detail-container{max-width:600px;margin:0 auto;padding:20px}.detail-close{text-align:right;margin-bottom:12px}.detail-close-btn{background:rgba(255,255,255,.15);border:none;color:#fff;width:32px;height:32px;border-radius:50%;cursor:pointer;font-size:20px}.detail-poster{width:100%;max-height:300px;object-fit:contain;border-radius:8px;background:#16162a;margin-bottom:16px}.detail-title{font-size:18px;font-weight:700;color:#fff;margin-bottom:12px}.detail-desc{font-size:13px;color:rgba(224,224,224,.8);line-height:1.6;margin-bottom:16px}.play-btn{display:block;width:100%;padding:12px;background:#2563eb;border:none;border-radius:8px;color:#fff;font-size:14px;font-weight:700;cursor:pointer;text-align:center;transition:background .2s;margin-bottom:10px}.play-btn:hover{background:#1d4ed8}.play-btn:disabled{background:#555;cursor:not-allowed}.play-btn.loading{background:#f59e0b}.play-btn.success{background:#10b981}.play-btn.error{background:#ef4444}.parse-status{text-align:center;font-size:12px;margin:6px 0;min-height:18px;color:rgba(224,224,224,.7)}.parse-status.loading{color:#2563eb}.parse-status.error{color:#ef4444}.parse-status.success{color:#10b981}.parse-log{background:#0a0a1a;border-radius:8px;padding:10px;margin-top:10px;max-height:150px;overflow-y:auto;font-family:monospace;font-size:11px;color:#888}.parse-log .log-line{margin:2px 0}.parse-log .log-success{color:#10b981}.parse-log .log-error{color:#ef4444}.parse-log .log-info{color:#2563eb}@media (min-width:600px){.list-grid{grid-template-columns:repeat(4,1fr)}}@media (min-width:900px){.list-grid{grid-template-columns:repeat(5,1fr)}}</style><div id=mainView><div class=header><div class=logo id=siteLogo>SupJav</div><div class=search-group><input class=search-box id=searchInput placeholder=搜索影片...> <button class=search-btn id=searchBtn><svg fill=none stroke=currentColor viewBox="0 0 24 24"xmlns=http://www.w3.org/2000/svg><path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"stroke-linecap=round stroke-linejoin=round stroke-width=2></path></svg></button></div></div><div class=category-bar id=categoryBar></div><div class=list-section><div class=list-grid id=listGrid></div><div id=listTip></div></div></div><div class=search-overlay id=searchOverlay><div class=search-nav><div class=search-title id=searchKeywordTitle>搜索结果</div><button class=detail-close-btn id=searchCloseBtn>×</button></div><div class=list-section><div class=list-grid id=searchListGrid></div><div id=searchListTip></div></div></div><div class=detail-overlay id=detailOverlay><div class=detail-container><div class=detail-close><button class=detail-close-btn id=detailClose>×</button></div><img alt=""class=detail-poster id=detailPoster src=""><div class=detail-title id=detailTitle>加载中...</div><div class=detail-desc id=detailDesc></div><button class=play-btn id=playBtn>正在解析...</button><div class=parse-status id=parseStatus></div><div class=parse-log id=parseLog style=display:none></div></div></div><script>!function(){"use strict";const l={siteName:"SupJav",siteUrl:"https://supjav.com",apiHost:"https://supjav.com/zh",gatewayHost:"https://lk1.supremejav.com",categories:[{id:"censored-jav",name:"有码"},{id:"uncensored-jav",name:"无码"},{id:"amateur",name:"素人"},{id:"chinese-subtitles",name:"中文字幕"},{id:"reducing-mosaic",name:"无码破解"},{id:"english-subtitles",name:"英文字幕"}],cache:{ttl:3e5,prefix:"supjav_"}},h={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36",Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","Accept-Language":"zh-CN,zh;q=0.9,en;q=0.8","Accept-Encoding":"gzip, deflate",Connection:"keep-alive","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"none","Upgrade-Insecure-Requests":"1"},c={currentCategory:"",currentPage:1,isLoading:!1,hasMore:!0,vodList:[],searchMode:!1,searchKeyword:"",searchPage:1,searchHasMore:!0,searchVodList:[],currentVod:null,detailData:null,m3u8Url:""},d=e=>document.querySelector(e);function u(e,t="info"){console.log(`[SupJav] ${e}`);const a=d("#parseLog");if(a){a.style.display="block";const r=document.createElement("div");r.className=`log-line log-${t}`,r.textContent=`> ${e}`,a.appendChild(r),a.scrollTop=a.scrollHeight}}function m(e,t=""){const a=d("#parseStatus");a&&(a.textContent=e,a.className=`parse-status ${t}`)}const a={getKey(e,t){return l.cache.prefix+"cat_"+(e||"home")+"_p"+t},isValid(e){return!!e&&Date.now()-e.timestamp<l.cache.ttl},async get(e){return window.fm?.cache?window.fm.cache.get(e):localStorage.getItem(e)||""},async set(e,t){window.fm?.cache?await window.fm.cache.set(e,t):localStorage.setItem(e,t)},async getCache(e,t){try{var a=this.getKey(e,t),r=a+"_meta",[s,i]=await Promise.all([this.get(a),this.get(r)]);if(!s||!i)return null;var n=JSON.parse(i);return this.isValid(n)?JSON.parse(s):null}catch(e){return null}},async setCache(e,t,a){try{var r=this.getKey(e,t),s=r+"_meta",i={timestamp:Date.now()};await Promise.all([this.set(r,JSON.stringify(a)),this.set(s,JSON.stringify(i))])}catch(e){}}};async function v(e,t=l.apiHost+"/",a="text",r=30){if(!window.fm?.req)throw new Error("fm.req 不可用");var s={...h,Referer:t};console.log(`[SupJav] GET: ${e}`),console.log(`[SupJav] Referer: ${t}`);s=await window.fm.req(e,{method:"GET",responseType:a,timeout:r,credentials:"include",headers:s});if(!s.ok)throw new Error(`HTTP ${s.status}: ${s.error||"请求失败"}`);return s.body}function g(e){return e?window.fm?.res?window.fm.res(e,{credentials:"include"}):e.startsWith("//")?"https:"+e:e.startsWith("/")?l.siteUrl+e:e:""}function s(e){const c=[],t=new DOMParser,a=t.parseFromString(e,"text/html"),r=a.querySelectorAll(".post");return r.forEach(e=>{const t=e.querySelector("a.img");if(t){var a=t.getAttribute("href");if(a){const i=e.querySelector("img.thumb");if(i){var r=i.getAttribute("data-original")||i.getAttribute("src")||"";const n=e.querySelector("h3 a");var s=n?.textContent.trim()||"";const o=e.querySelector(".meta");e=o?.textContent?.match(/\d{4}\/\d{2}\/\d{2}/)?.[0]||"";c.push({vod_id:a,vod_name:s,vod_pic:r,vod_remarks:e})}}}}),c}function p(){return l.apiHost+"/"}async function i(e,t){let a,r;return r=e?(a=`${l.apiHost}/category/${e}/page/${t}`,1<t?`${l.apiHost}/category/${e}/page/${t-1}`:`${l.apiHost}/category/${e}/`):(a=1<t?`${l.apiHost}/page/${t}`:l.apiHost+"/",1<t?`${l.apiHost}/page/${t-1}`:l.apiHost+"/"),s(await v(a,r))}async function n(e,t){let a,r;return r=1<t?(a=`${l.apiHost}/page/${t}/?s=${encodeURIComponent(e)}`,`${l.apiHost}/page/${t-1}/?s=${encodeURIComponent(e)}`):(a=`${l.apiHost}/?s=${encodeURIComponent(e)}`,l.apiHost+"/"),s(await v(a,r))}async function y(e){var t=e.startsWith("http")?e:l.apiHost+e;u(`正在获取详情页: ${t}`,"info");var a=await v(t,p());const r=/class=["']btn-server[^"']*["'][\s\S]*?data-link=["']([a-f0-9]+)["']>([^<]+)</gi;let s,i=null;for(;null!==(s=r.exec(a));)if("FST"===s[2].trim().toUpperCase()){i=s[1],u(`找到 FST 线路: ${i}`,"success");break}if(!i)throw new Error("未找到 FST 播放线路");e=i.split("").reverse().join(""),e=`${l.gatewayHost}/supjav.php?c=${e}`;if(u(`FST 网关地址: ${e}`,"info"),u("正在请求 FST 网关...","info"),!window.fm?.req)throw new Error("fm.req 不可用");t={...h,Referer:t,"Sec-Fetch-Site":"none"},t=await window.fm.req(e,{method:"GET",responseType:"text",timeout:30,credentials:"include",headers:t});if(!t.ok)throw new Error(`网关请求失败: ${t.status}`);const n=t.body;u("网关响应获取成功,开始解密...","info");t=n.match(/eval\(function\(p,a,c,k,e,d\)[\s\S]*?\.split\('\|'\)\)\)/);if(!t)throw new Error("FST 网关响应体中未包含所需的 eval 混淆打包数据");u("锁定加密块,正在执行反混淆脱壳...","info");const o=function(e){try{const t=new Function(e.replace(/^eval/,"return"));return t()}catch(e){return"Packer 还原出错: "+e.message}}(t[0]);u("反混淆纯化完毕,扫描流配置项...","info");let c="";const d=o.match(/"(https?:\/\/[^"]+?\.m3u8[^"]*?)"/i)||o.match(/file\s*:\s*["']([^"']+?\.m3u8[^"']*?)["']/i);return d&&(c=d[1].replace(/\\/g,""),u(`解析成功: ${c}`,"success")),c}async function f(e){var t=await v(e.startsWith("http")?e:l.apiHost+e,p());const a=new DOMParser,r=a.parseFromString(t,"text/html"),s=r.querySelector("h3 a")||r.querySelector("h1")||r.querySelector("title");var i=s?.textContent?.trim()||"影片详情";const n=r.querySelector(".img img")||r.querySelector("img");let o="";n&&(o=n.getAttribute("data-original")||n.getAttribute("src")||"");const c=r.querySelector(".info-content, .description, .entry-content");t=c?.textContent?.trim()||"";return{vod_id:e,vod_name:i,vod_pic:o,vod_content:t}}function o(e=!1,t=[]){const n=c.searchMode?d("#searchListGrid"):d("#listGrid"),a=c.searchMode?d("#searchListTip"):d("#listTip");if(n&&a){if(!e){n.innerHTML="";var r=c.searchMode?c.searchVodList:c.vodList;if(c.isLoading)return void(a.innerHTML='<div class="loading">加载中...</div>');if(0===r.length)return void(a.innerHTML='<div class="error-tip">暂无内容</div>')}const s=e?t:c.searchMode?c.searchVodList:c.vodList,o=e?c.searchMode?c.searchVodList.length-t.length:c.vodList.length-t.length:0;s.forEach((e,t)=>{var a=g(e.vod_pic),r=e.vod_remarks||"";const s=o+t,i=document.createElement("div");i.className="item",i.innerHTML=`
|
||||
<div class="item-img-wrap">
|
||||
<img class="item-img" src="${a}" loading="lazy" decoding="async" alt="${e.vod_name}" />
|
||||
${r?`<span class="img-year">${r}</span>`:""}
|
||||
</div>
|
||||
<div class="item-title">${e.vod_name}</div>
|
||||
`,i.addEventListener("click",()=>{!async function t(a){c.currentVod=a;c.detailData=null;c.m3u8Url="";d("#detailOverlay").classList.add("active");d("#detailPoster").src=g(a.vod_pic);d("#detailTitle").textContent=a.vod_name;d("#detailDesc").textContent="正在加载详情...";d("#playBtn").textContent="正在解析 FST 线路...";d("#playBtn").disabled=!0;d("#playBtn").className="play-btn loading";d("#parseStatus").textContent="";d("#parseLog").innerHTML="";d("#parseLog").style.display="none";try{const e=await f(a.vod_id);c.detailData=e,d("#detailTitle").textContent=e.vod_name,d("#detailDesc").textContent=e.vod_content||"无描述",m("正在解析 FST 线路...","loading"),u("开始 FST 线路解析流程","info");const r=await y(a.vod_id);if(!r)throw new Error("未能从 FST 线路解析出播放地址");c.m3u8Url=r,d("#playBtn").textContent="▶ 播放 FST 直链",d("#playBtn").className="play-btn success",d("#playBtn").disabled=!1,d("#playBtn").onclick=()=>w(),m("解析成功!","success"),u("FST 直链解析完成!","success")}catch(e){d("#playBtn").textContent="解析失败,点击重试",d("#playBtn").className="play-btn error",d("#playBtn").disabled=!1,d("#playBtn").onclick=()=>t(a),m("解析失败: "+e.message,"error"),u("解析失败: "+e.message,"error")}}((c.searchMode?c.searchVodList:c.vodList)[s])}),n.appendChild(i)});t=c.searchMode?c.searchHasMore:c.hasMore;a.innerHTML=t?'<div class="load-more">上滑加载更多</div>':'<div class="no-more">没有更多了</div>'}}function r(){d("#detailOverlay").classList.remove("active"),c.currentVod=null}async function w(){if(c.m3u8Url)if(window.fm?.play)try{await window.fm.play(c.m3u8Url,c.detailData?.vod_name||"视频",{})}catch(e){u("播放失败: "+e.message,"error"),alert("播放失败: "+e.message)}else alert("当前环境不支持内置播放")}async function L(e){if(e?.trim()){c.searchMode=!0,c.searchKeyword=e.trim(),c.searchPage=1,c.searchVodList=[],c.searchHasMore=!0,c.isLoading=!0,d("#searchKeywordTitle").textContent=`搜索: "${c.searchKeyword}"`,d("#searchOverlay").classList.add("active"),o(!1);try{var t=await n(c.searchKeyword,1);c.searchVodList=t,c.searchHasMore=0<t.length}catch(e){c.searchVodList=[]}o(c.isLoading=!1)}}function e(){d("#searchOverlay").classList.remove("active"),c.searchMode=!1,c.searchKeyword="",c.searchVodList=[]}async function M(e=!1){if(!e&&1===c.currentPage){e=await a.getCache(c.currentCategory,c.currentPage);if(e)return c.vodList=e,void o(!1)}o(!(c.isLoading=!0));try{var t=await i(c.currentCategory,c.currentPage);c.vodList=t,c.hasMore=0<t.length,1===c.currentPage&&await a.setCache(c.currentCategory,c.currentPage,c.vodList)}catch(e){c.vodList=[]}o(c.isLoading=!1)}function t(){let r=null;var e=()=>{r=r||setTimeout(()=>{r=null;var e=c.searchMode?d("#searchOverlay"):window,t=c.searchMode?e.scrollTop:window.scrollY||document.documentElement.scrollTop,a=c.searchMode?e.clientHeight:window.innerHeight,e=(c.searchMode?e:document.documentElement).scrollHeight;(e-t-a<450||.85*e<t+a)&&async function(){if(!c.isLoading){if(c.searchMode){if(!c.searchHasMore)return;c.isLoading=!0;const r=d("#searchListTip");r&&(r.innerHTML='<div class="loading">正在加载更多搜索结果...</div>');var e=c.searchPage+1;try{var t=await n(c.searchKeyword,e);t&&0<t.length?(c.searchPage=e,c.searchVodList=[...c.searchVodList,...t],o(c.searchHasMore=!0,t)):(c.searchHasMore=!1,r&&(r.innerHTML='<div class="no-more">没有更多了</div>'))}catch(e){r&&(r.innerHTML='<div class="load-more">网络异常,滑动重试</div>')}}else{if(!c.hasMore)return;c.isLoading=!0;const s=d("#listTip");s&&(s.innerHTML='<div class="loading">正在加载更多...</div>');t=c.currentPage+1;try{var a=await i(c.currentCategory,t);a&&0<a.length?(c.currentPage=t,c.vodList=[...c.vodList,...a],o(c.hasMore=!0,a)):(c.hasMore=!1,s&&(s.innerHTML='<div class="no-more">没有更多了</div>'))}catch(e){s&&(s.innerHTML='<div class="load-more">网络异常,滑动重试</div>')}}c.isLoading=!1}}()},100)};window.addEventListener("scroll",e,{passive:!0}),d("#searchOverlay").addEventListener("scroll",e,{passive:!0})}!async function(){await async function(){if((window.fongmiBridge||window.fm||window.fongmi)&&document.documentElement.classList.add("fm-native"),window.fm||await new Promise(e=>{const t=()=>{window.fm?e():setTimeout(t,100)};window.addEventListener("fmsdk",t,{once:!0}),setTimeout(e,3e3)}),window.fm?.site)try{var e=await window.fm.site();c.siteKey=e.key||"supjav";const t=d("#siteLogo");t&&(t.textContent=e.name||l.siteName)}catch(e){}}(),function(){const a=d("#categoryBar");if(a){let t='<div class="category-item active" data-id="">首页</div>';l.categories.forEach(e=>{t+=`<div class="category-item" data-id="${e.id}">${e.name}</div>`}),a.innerHTML=t,a.querySelectorAll(".category-item").forEach(t=>{t.addEventListener("click",()=>{var e=t.dataset.id||"";e===c.currentCategory&&0<c.vodList.length&&!c.searchMode||(a.querySelectorAll(".category-item").forEach(e=>e.classList.remove("active")),t.classList.add("active"),c.searchMode=!1,d("#searchOverlay").classList.remove("active"),c.currentCategory=e,c.currentPage=1,c.vodList=[],M(!(c.hasMore=!0)))})})}}(),function(){d("#searchBtn")?.addEventListener("click",()=>{L(d("#searchInput")?.value)}),d("#searchInput")?.addEventListener("keypress",e=>{"Enter"===e.key&&L(e.target.value)}),d("#detailClose")?.addEventListener("click",r),d("#detailOverlay")?.addEventListener("click",e=>{e.target===e.currentTarget&&r()}),d("#searchCloseBtn")?.addEventListener("click",e);let t=0;document.addEventListener("touchstart",e=>{t=e.touches[0].clientY},{passive:!0}),document.addEventListener("touchend",e=>{80<t-e.changedTouches[0].clientY&&window.scrollY<50&&!c.isLoading&&!c.searchMode&&(c.currentPage=1,c.vodList=[],M(c.hasMore=!0))},{passive:!0})}(),t(),await M(!1)}().catch(console.error)}()</script>
|
||||
@@ -0,0 +1,22 @@
|
||||
<!doctypehtml><html lang=zh-CN><meta charset=UTF-8><meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"name=viewport><title>Wuma</title><style>*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}body,html{min-height:100%;background:0 0}html:not(.fm-native),html:not(.fm-native) body{background:#1a1a2e}body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;color:#e0e0e0}.header{height:52px;background:#16162a;display:flex;align-items:center;padding:0 12px;gap:10px;position:sticky;top:0;z-index:100}.logo{font-size:18px;font-weight:700;white-space:nowrap;color:#fff}.search-group{flex:1;display:flex;align-items:center;background:#2a2a4a;border-radius:25px;padding-right:4px}.search-group .search-box{flex:1;border:none;background:0 0;outline:0;padding:0 10px;height:34px;color:#e0e0e0;font-size:12px}.search-group .search-btn{width:32px;height:28px;background:#e0592f;border:none;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer}.search-group .search-btn svg{width:14px;height:14px;color:#fff}.category-bar{display:flex;gap:8px;padding:8px 12px;overflow-x:auto;background:#16162a}.category-bar::-webkit-scrollbar{display:none}.category-item{padding:6px 16px;border-radius:20px;background:rgba(42,42,74,.6);font-size:13px;white-space:nowrap;color:#e0e0e0;cursor:pointer;transition:all .2s}.category-item.active{background:#e0592f;color:#fff}.list-section{background:rgba(26,26,46,.5);padding:12px}.list-item{display:flex;flex-direction:row;background:#16162a;border-radius:10px;margin-bottom:10px;overflow:hidden;cursor:pointer;transition:transform .2s,box-shadow .2s}.list-item:active{transform:scale(.98)}.list-item-img-wrap{position:relative;width:120px;min-width:120px;flex-shrink:0}.list-item-img{width:120px;height:90px;object-fit:cover;display:block;background:#2a2a4a}.list-item-play-icon{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:36px;height:36px;background:rgba(0,0,0,.6);border-radius:50%;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .2s}.list-item:hover .list-item-play-icon{opacity:1}.list-item-play-icon svg{width:16px;height:16px;fill:#fff;margin-left:2px}.list-item-content{flex:1;padding:10px 12px;display:flex;flex-direction:column;justify-content:center;min-width:0}.list-item-title{font-size:14px;font-weight:500;color:#fff;line-height:1.4;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;margin-bottom:6px}.list-item-meta{font-size:12px;color:rgba(224,224,224,.5)}.loading{text-align:center;padding:20px;color:rgba(224,224,224,.5);font-size:12px}.loading-spinner{width:24px;height:24px;border:3px solid rgba(224,224,224,.2);border-top-color:#e0592f;border-radius:50%;animation:spin 1s linear infinite;margin:0 auto 10px}@keyframes spin{to{transform:rotate(360deg)}}.error-tip{text-align:center;padding:40px;color:#e0592f;font-size:14px}.load-more,.no-more{text-align:center;padding:20px;color:rgba(224,224,224,.5);font-size:12px;width:100%}.parse-toast{position:fixed;top:70px;left:50%;transform:translateX(-50%);background:rgba(22,22,42,.95);border:1px solid #e0592f;border-radius:8px;padding:12px 20px;z-index:1000;text-align:center;min-width:200px;box-shadow:0 4px 20px rgba(0,0,0,.5)}.parse-toast.success{border-color:#10b981}.parse-toast.error{border-color:#ef4444}.parse-toast-title{font-size:14px;font-weight:700;color:#fff;margin-bottom:4px}.parse-toast-msg{font-size:12px;color:rgba(224,224,224,.7)}.search-overlay{position:fixed;inset:0;background:#1a1a2e;z-index:150;display:none;overflow-y:auto}.search-overlay.active{display:block}.search-nav{height:52px;background:#16162a;display:flex;align-items:center;justify-content:space-between;padding:0 16px;position:sticky;top:0;z-index:10}.search-title{font-size:16px;font-weight:700;color:#fff}@media (min-width:600px){.list-item-img-wrap{width:160px;min-width:160px}.list-item-img{width:160px;height:120px}.list-item-title{font-size:15px}}</style><div id=mainView><div class=header><div class=logo id=siteLogo>Wuma</div><div class=search-group><input class=search-box id=searchInput placeholder=搜索影片...> <button class=search-btn id=searchBtn><svg fill=none stroke=currentColor viewBox="0 0 24 24"xmlns=http://www.w3.org/2000/svg><path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"stroke-linecap=round stroke-linejoin=round stroke-width=2></path></svg></button></div></div><div class=category-bar id=categoryBar></div><div class=list-section><div id=listContainer></div><div id=listTip></div></div></div><div class=search-overlay id=searchOverlay><div class=search-nav><div class=search-title id=searchKeywordTitle>搜索结果</div><button class=detail-close-btn id=searchCloseBtn style=background:rgba(255,255,255,.15);border:none;color:#fff;width:32px;height:32px;border-radius:50%;cursor:pointer;font-size:20px>×</button></div><div class=list-section><div id=searchListContainer></div><div id=searchListTip></div></div></div><div class=parse-toast id=parseToast style=display:none><div class=parse-toast-title id=toastTitle>解析中...</div><div class=parse-toast-msg id=toastMsg>正在获取直链</div></div><script>!function(){"use strict";const d={siteName:"Wuma",siteUrl:"https://wuma.vc",apiBase:"https://wuma.vc/api/v1",listPageSize:5,searchPageSize:10,categories:[{id:"20",name:"日韩"},{id:"21",name:"亚洲"},{id:"22",name:"自拍"},{id:"23",name:"欧美"},{id:"24",name:"动漫"}],cache:{ttl:3e5,prefix:"wuma_"}},l={currentCategory:"",currentStart:0,isLoading:!1,hasMore:!0,vodList:[],searchMode:!1,searchKeyword:"",searchStart:0,searchHasMore:!0,searchVodList:[]},h=e=>document.querySelector(e);let s=null;function a(e,t,a=""){const i=h("#parseToast");i&&(h("#toastTitle").textContent=e,h("#toastMsg").textContent=t,i.className="parse-toast "+a,i.style.display="block",s&&clearTimeout(s),s=setTimeout(()=>{i.style.display="none"},3e3))}const i={getKey(e,t,a){t="search"===e?"search_"+l.searchKeyword:t||"home";return d.cache.prefix+"list_"+t+"_s"+a},isValid(e){return!!e&&Date.now()-e.timestamp<d.cache.ttl},async get(e){return window.fm?.cache?window.fm.cache.get(e):localStorage.getItem(e)||""},async set(e,t){window.fm?.cache?await window.fm.cache.set(e,t):localStorage.setItem(e,t)},async getCache(e,t,a){try{var i=this.getKey(e,t,a),s=i+"_meta",[r,n]=await Promise.all([this.get(i),this.get(s)]);if(!r||!n)return null;var c=JSON.parse(n);return this.isValid(c)?JSON.parse(r):null}catch(e){return null}},async setCache(e,t,a,i){try{var s=this.getKey(e,t,a),r=s+"_meta",n={timestamp:Date.now()};await Promise.all([this.set(s,JSON.stringify(i)),this.set(r,JSON.stringify(n))])}catch(e){}}};async function r(e){if(console.log("[Wuma] GET:",e),!window.fm?.req)throw new Error("fm.req 不可用");var t={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36",Accept:"application/json, text/plain, */*","Accept-Language":"zh-CN,zh;q=0.9,en;q=0.8","Accept-Encoding":"gzip, deflate",Connection:"keep-alive",Referer:d.siteUrl+"/",Origin:d.siteUrl},t=await window.fm.req(e,{method:"GET",responseType:"json",timeout:30,credentials:"include",headers:t});if(!t.ok)throw new Error(`HTTP ${t.status}: ${t.error||"请求失败"}`);return t.body}async function n(e,t){let a=`${d.apiBase}/videos?start=${t}&count=${d.listPageSize}&sort=-publishedAt&skipCount=true&nsfw=both&nsfwFlagsExcluded=0&nsfwFlagsIncluded=0&isLive=false`;e&&(a+=`&categoryOneOf=${e}`);const i=await r(a);return i.data&&Array.isArray(i.data)?i.data.map(e=>({uuid:e.uuid,name:e.name,thumbnailPath:e.thumbnailPath})):[]}async function c(e,t){const a=await r(`${d.apiBase}/search/videos?start=${t}&count=${d.searchPageSize}&search=${encodeURIComponent(e)}&sort=-match&searchTarget=local&nsfw=both&nsfwFlagsIncluded=0`);return a.data&&Array.isArray(a.data)?a.data.map(e=>({uuid:e.uuid,name:e.name,thumbnailPath:e.thumbnailPath})):[]}async function m(e){a("解析中...",`正在获取 ${e.name} 的直链`,"");try{var t=await async function(e){if(!(e=await r(`${d.apiBase}/videos/${e}`)).streamingPlaylists||0===e.streamingPlaylists.length)throw new Error("未找到播放源");if(!(e=e.streamingPlaylists[0].playlistUrl))throw new Error("未找到播放地址");return e}(e.uuid);a("解析成功!","正在调起播放器...","success"),window.fm?.play?await window.fm.play(t,e.name,{}):a("播放失败","当前环境不支持内置播放","error")}catch(e){console.error("[Wuma] 播放错误:",e),a("解析失败",e.message,"error")}}function o(e=!1,t=[]){const s=l.searchMode,r=s?h("#searchListContainer"):h("#listContainer"),a=s?h("#searchListTip"):h("#listTip");var i=s?l.searchVodList:l.vodList,n=s?l.searchHasMore:l.hasMore;if(r&&a){if(!e){if(r.innerHTML="",l.isLoading)return void(a.innerHTML=`
|
||||
<div class="loading">
|
||||
<div class="loading-spinner"></div>
|
||||
<div>加载中...</div>
|
||||
</div>
|
||||
`);if(0===i.length)return void(a.innerHTML='<div class="error-tip">暂无内容</div>')}const c=e?t:i,o=e?i.length-t.length:0;c.forEach((e,t)=>{const a=o+t;t=(t=e.thumbnailPath)?window.fm?.res?window.fm.res(d.siteUrl+t,{credentials:"include"}):t.startsWith("http")?t:d.siteUrl+t:"";const i=document.createElement("div");i.className="list-item",i.innerHTML=`
|
||||
<div class="list-item-img-wrap">
|
||||
<img class="list-item-img" src="${t}" loading="lazy" decoding="async" alt="${e.name}" />
|
||||
<div class="list-item-play-icon">
|
||||
<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-item-content">
|
||||
<div class="list-item-title">${e.name}</div>
|
||||
<div class="list-item-meta">点击播放</div>
|
||||
</div>
|
||||
`,i.addEventListener("click",()=>{m((s?l.searchVodList:l.vodList)[a])}),r.appendChild(i)}),a.innerHTML=n?'<div class="load-more">加载更多</div>':'<div class="no-more">没有更多了</div>'}}async function u(e=!1){if(!e&&0===l.currentStart){e=await i.getCache("list",l.currentCategory,l.currentStart);if(e)return l.vodList=e,void o(!1)}o(!(l.isLoading=!0));try{var t=await n(l.currentCategory,l.currentStart);l.vodList=t,l.hasMore=t.length===d.listPageSize,0===l.currentStart&&await i.setCache("list",l.currentCategory,l.currentStart,l.vodList)}catch(e){console.error("[Wuma] 加载错误:",e),l.vodList=[]}o(l.isLoading=!1)}async function v(){if(!l.isLoading){if(l.searchMode){if(!l.searchHasMore)return}else if(!l.hasMore)return;l.isLoading=!0;const a=l.searchMode?h("#searchListTip"):h("#listTip");a&&(a.innerHTML=`
|
||||
<div class="loading">
|
||||
<div class="loading-spinner"></div>
|
||||
<div>加载中...</div>
|
||||
</div>
|
||||
`);let t,e;e=l.searchMode?(t=l.searchStart+d.searchPageSize,d.searchPageSize):(t=l.currentStart+d.listPageSize,d.listPageSize);try{let e;e=l.searchMode?await c(l.searchKeyword,t):await n(l.currentCategory,t),e&&0<e.length?(l.searchMode?(l.searchStart=t,l.searchVodList=[...l.searchVodList,...e],l.searchHasMore=e.length===d.searchPageSize):(l.currentStart=t,l.vodList=[...l.vodList,...e],l.hasMore=e.length===d.listPageSize),o(!0,e)):(l.searchMode?l.searchHasMore=!1:l.hasMore=!1,a&&(a.innerHTML='<div class="no-more">没有更多了</div>'))}catch(e){console.error("[Wuma] 加载更多错误:",e),a&&(a.innerHTML='<div class="load-more">加载失败,点击重试</div>')}l.isLoading=!1}}async function t(e){if(e?.trim()){l.searchMode=!0,l.searchKeyword=e.trim(),l.searchStart=0,l.searchVodList=[],l.searchHasMore=!0,l.isLoading=!0,h("#searchKeywordTitle").textContent=`搜索: "${l.searchKeyword}"`,h("#searchOverlay").classList.add("active"),o(!1);try{var t=await c(l.searchKeyword,0);l.searchVodList=t,l.searchHasMore=t.length===d.searchPageSize}catch(e){console.error("[Wuma] 搜索错误:",e),l.searchVodList=[]}o(l.isLoading=!1)}}function g(){h("#searchOverlay").classList.remove("active"),l.searchMode=!1,l.searchKeyword="",l.searchStart=0,l.searchVodList=[]}!async function(){await async function(){if((window.fongmiBridge||window.fm||window.fongmi)&&document.documentElement.classList.add("fm-native"),window.fm||await new Promise(e=>{const t=()=>{window.fm?e():setTimeout(t,100)};window.addEventListener("fmsdk",t,{once:!0}),setTimeout(e,3e3)}),window.fm?.site)try{var e=await window.fm.site();const t=h("#siteLogo");t&&(t.textContent=e.name||d.siteName)}catch(e){}}(),function(){const a=h("#categoryBar");if(a){let t='<div class="category-item active" data-id="">全部</div>';d.categories.forEach(e=>{t+=`<div class="category-item" data-id="${e.id}">${e.name}</div>`}),a.innerHTML=t,a.querySelectorAll(".category-item").forEach(t=>{t.addEventListener("click",()=>{var e=t.dataset.id||"";e===l.currentCategory&&0<l.vodList.length&&!l.searchMode||(a.querySelectorAll(".category-item").forEach(e=>e.classList.remove("active")),t.classList.add("active"),g(),l.currentCategory=e,l.currentStart=0,l.vodList=[],u(!(l.hasMore=!0)))})})}}(),h("#searchBtn")?.addEventListener("click",()=>{t(h("#searchInput")?.value)}),h("#searchInput")?.addEventListener("keypress",e=>{"Enter"===e.key&&t(e.target.value)}),h("#searchCloseBtn")?.addEventListener("click",g),document.addEventListener("click",e=>{e.target.classList.contains("load-more")&&v()}),function(){let a=null;window.addEventListener("scroll",()=>{l.searchMode||(a=a||setTimeout(()=>{a=null;var e=window.scrollY||document.documentElement.scrollTop,t=window.innerHeight;document.documentElement.scrollHeight-e-t<200&&v()},100))},{passive:!0}),h("#searchOverlay")?.addEventListener("scroll",()=>{var e,t,a;!l.searchMode||(e=h("#searchOverlay"))&&(t=e.scrollTop,a=e.clientHeight,e.scrollHeight-t-a<200&&v())},{passive:!0})}(),await u(!1)}().catch(console.error)}()</script>
|
||||
@@ -0,0 +1,529 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" />
|
||||
<title>永樂視頻 WebHome</title>
|
||||
<style>
|
||||
:root{--panel-rgb:42,50,58;--panel:rgba(var(--panel-rgb),.62);--panel-soft:rgba(var(--panel-rgb),.44);--panel-strong:rgba(var(--panel-rgb),.86);--line:rgba(255,255,255,.16);--text:#f7fbff;--muted:rgba(247,251,255,.68);--accent:#ff6b35;--accent2:#ffb86b;--bad:#ff5c7a;--ok:#68e39a;--radius:18px;--shadow:0 18px 50px rgba(0,0,0,.28)}
|
||||
*{box-sizing:border-box} html,body{margin:0;min-height:100%;background:transparent;color:var(--text);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",Arial,sans-serif} html:not(.fm-native),html:not(.fm-native) body{background:#222a32}.app{min-height:100vh;background:transparent;padding:10px 10px 28px}.top{position:relative;z-index:1;background:transparent;backdrop-filter:none;padding:8px 0 10px;border-bottom:0}.bar{display:flex;align-items:center;gap:8px;min-width:0;}.brand{flex:0 0 auto;font-size:21px;font-weight:900;letter-spacing:.5px;text-shadow:0 2px 14px rgba(0,0,0,.35)}.brand b{color:var(--accent2)}.search{flex:1;display:flex;gap:8px;min-width:0;align-items:center;background:rgba(255,255,255,.10);border:1px solid rgba(255,255,255,.14);border-radius:999px;padding:6px 7px 6px 13px;box-shadow:inset 0 1px 0 rgba(255,255,255,.05)}.search input{flex:1;min-width:0;background:transparent;border:0;outline:0;color:var(--text);font-size:15px}.search input::placeholder{color:var(--muted)}button,.btn{border:1px solid var(--line);background:var(--panel-soft);color:var(--text);border-radius:999px;padding:8px 12px;font-size:14px;cursor:pointer}.btn.primary,button.primary{border-color:rgba(255,107,53,.45);background:linear-gradient(135deg,rgba(255,107,53,.90),rgba(255,184,107,.72));color:#ffffff;font-weight:800}.tabs{display:flex;gap:8px;overflow:auto;padding:10px 2px 0;scrollbar-width:none}.tabs::-webkit-scrollbar{display:none}.tab{white-space:nowrap;border-radius:999px;padding:8px 14px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.12);color:var(--muted)}.tab.active{background:linear-gradient(135deg,rgba(255,107,53,.55),rgba(255,184,107,.30));color:var(--text);border-color:rgba(255,184,107,.58);font-weight:800}.hero{margin:10px 0 16px;padding:18px;border-radius:var(--radius);background:linear-gradient(135deg,rgba(255,107,53,.28),rgba(42,50,58,.54));border:1px solid var(--line);box-shadow:var(--shadow)}.hero h1{margin:0 0 8px;font-size:24px}.hero p{margin:0;color:var(--muted);line-height:1.6}.status{margin:10px 2px;color:var(--muted);font-size:14px}.grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.card{position:relative;min-width:0;border-radius:15px;overflow:hidden;background:linear-gradient(180deg,rgba(255,255,255,.09),rgba(255,255,255,.045));border:1px solid rgba(255,255,255,.12);box-shadow:0 10px 28px rgba(0,0,0,.22);cursor:pointer}.poster{aspect-ratio:3/4;background:rgba(255,255,255,.08);overflow:hidden;position:relative}.poster img{width:100%;height:100%;object-fit:cover;display:block;filter:saturate(1.04) contrast(1.03)}.poster .noimg{height:100%;display:flex;align-items:center;justify-content:center;color:var(--muted);font-size:28px}.meta{padding:8px 8px 10px;background:linear-gradient(180deg,rgba(18,24,30,.15),rgba(18,24,30,.44))}.name{font-weight:800;font-size:13.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.remark{margin-top:4px;color:var(--muted);font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.section{margin:16px 0}.section-title{display:flex;align-items:center;justify-content:space-between;margin:0 2px 10px}.section-title h2{margin:0;font-size:17px;letter-spacing:.3px}.rank{display:grid;gap:8px}.rank-item{display:flex;gap:10px;align-items:center;padding:11px 12px;border-radius:14px;background:var(--panel-soft);border:1px solid var(--line);cursor:pointer}.rank-no{width:32px;height:32px;display:grid;place-items:center;border-radius:10px;background:rgba(255,107,53,.22);font-weight:900;color:var(--accent2)}.rank-title{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sheet{position:fixed;inset:0;z-index:40;display:none;background:transparent;overflow:hidden}.sheet.active{display:block}.sheet-inner{height:100vh;min-height:100vh;padding:12px;background:transparent;overflow-y:auto;-webkit-overflow-scrolling:touch;overscroll-behavior:contain;touch-action:pan-y}.detail-panel{background:linear-gradient(135deg,rgba(64,35,30,.68),rgba(30,24,24,.54));border:1px solid rgba(255,255,255,.10);border-radius:22px;box-shadow:var(--shadow);padding:18px 14px 22px;margin-bottom:42px;touch-action:pan-y}.detail-head{display:grid;grid-template-columns:38% 1fr;gap:16px;align-items:start}.detail-pic{width:100%;aspect-ratio:3/4;border-radius:16px;object-fit:cover;background:rgba(255,255,255,.08);box-shadow:0 12px 30px rgba(0,0,0,.32)}.detail-title{font-size:25px;font-weight:500;margin:0 0 9px;line-height:1.25;color:rgba(255,255,255,.94)}.detail-info{color:rgba(255,255,255,.76);line-height:1.85;font-size:18px;font-weight:500}.detail-info b{color:rgba(255,255,255,.68);font-weight:800;margin-right:8px}.detail-date{font-size:20px;color:rgba(255,255,255,.88);margin:2px 0 14px}.intro-pill{display:inline-flex;margin:28px 0 18px;padding:11px 25px;border-radius:999px;background:#25b86b;color:white;font-size:18px;font-weight:700;box-shadow:0 8px 24px rgba(37,184,107,.22)}.desc{margin:0 4px 18px;color:rgba(255,255,255,.66);font-size:20px;line-height:1.95;max-height:none;overflow:visible}.play-title{margin:18px 2px 8px;font-size:18px;font-weight:800;color:rgba(255,255,255,.9)}.line-tabs{padding:4px;display:flex;gap:8px;overflow-x:auto;margin:8px 0 8px}.line-tab{border-radius:999px;padding:8px 12px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.14);white-space:nowrap;color:rgba(255,255,255,.82)}.line-tab.active{background:rgba(255,107,53,.36);border-color:rgba(255,184,107,.55);font-weight:800}.episodes{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px;max-height:none;overflow:visible;touch-action:pan-y}.ep{border-radius:12px;padding:9px 11px;background:rgba(255,255,255,.09);border:1px solid rgba(255,255,255,.14);cursor:pointer;min-width:68px;color:rgba(255,255,255,.86)}.empty{padding:30px 18px;text-align:center;color:var(--muted);background:var(--panel-soft);border:1px dashed var(--line);border-radius:var(--radius)}.loading{opacity:.75}.focusable:focus,.card:focus,.rank-item:focus,.ep:focus,button:focus,.tab:focus,input:focus{outline:2px solid rgba(255,184,107,.76);outline-offset:2px;transform:translateY(-1px)}
|
||||
body.detail-active .app{visibility:hidden;pointer-events:none}@media (min-width:720px){.grid{grid-template-columns:repeat(5,minmax(0,1fr))}.app{padding:18px 28px 44px}.hero h1{font-size:30px}.detail-head{grid-template-columns:160px 1fr}.detail-pic{width:160px}}@media (min-width:1100px){.grid{grid-template-columns:repeat(7,minmax(0,1fr))}.app{max-width:1380px;margin:0 auto}.sheet-inner{max-width:1180px;margin:0 auto}}
|
||||
@media (max-width:480px){.app{padding:8px 9px 24px}.brand{font-size:20px}.search input{font-size:14px}.search button{padding:7px 10px}.grid{grid-template-columns:repeat(3,minmax(0,1fr));gap:9px}.meta{padding:7px 7px 9px}.name{font-size:13px}.remark{font-size:11px}.detail-head{grid-template-columns:38% 1fr;gap:14px}.detail-title{font-size:24px}.detail-date{font-size:20px}.detail-info{font-size:18px;line-height:1.9}.desc{font-size:20px;line-height:1.92}.sheet-inner{padding:10px}.detail-panel{border-radius:20px;padding:16px 12px}.ep{min-width:64px;padding:8px 9px}}
|
||||
#closeDetail{margin:0 0 18px 0;padding:10px 20px;border-radius:999px;background:rgba(255,255,255,.10);border:1px solid rgba(255,184,107,.55)}
|
||||
.detail-head{align-items:start!important;grid-template-columns:38% 1fr!important;gap:16px!important}
|
||||
.detail-title{font-size:24px!important;font-weight:600!important;line-height:1.25!important;margin:0 0 10px!important;word-break:break-all}
|
||||
.detail-info:empty{display:none!important}
|
||||
.detail-info div{margin:4px 0}
|
||||
.intro-pill{margin-top:26px!important}
|
||||
.episodes{display:grid!important;grid-template-columns:repeat(4,minmax(0,1fr))!important;gap:12px!important;width:100%!important;grid-auto-rows: 42px}
|
||||
.ep{width:100%!important;min-width:0!important;text-align:center!important;padding:0!important;height:100%!important;border-radius:14px!important;display:flex;align-items:center;justify-content:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
||||
@media(max-width:480px){.episodes{grid-template-columns:repeat(4,minmax(0,1fr))!important;gap:10px!important}.ep{font-size:12px!important;padding:9px 2px!important}.detail-title{font-size:23px!important}}
|
||||
|
||||
/* detail final tune */
|
||||
.detail-side{max-height:100%;overflow:hidden}
|
||||
.detail-info{font-size:15px!important;line-height:1.55!important;font-weight:500!important;}
|
||||
.detail-info b{font-size:15px!important;margin-right:3px!important}
|
||||
.detail-title{font-size:22px!important;margin-bottom:8px!important;font-weight:650!important}
|
||||
.desc{font-size:17px!important;line-height:1.75!important;color:rgba(255,255,255,.68)!important}
|
||||
.intro-pill{background:rgba(255,255,255,.10)!important;border:1px solid rgba(255,184,107,.55)!important;color:rgba(255,255,255,.94)!important;box-shadow:none!important;padding:10px 22px!important;font-size:17px!important}
|
||||
@media(max-width:480px){.detail-info{font-size:15px!important;line-height:1.55!important;max-height:calc((100vw - 44px) * .38 * 1.333 - 42px)}.detail-info b{font-size:15px!important}.detail-title{font-size:22px!important}.desc{font-size:17px!important;line-height:1.75!important}}
|
||||
.pager-wrap{display:flex;align-items:center;flex-wrap:nowrap;gap:4px;margin:12px 0;}
|
||||
.pager-btn{flex:1 1 0;min-width:0;padding:6px 2px;font-size:12px;border-radius:10px;border:1px solid var(--line);background:rgba(255,255,255,.08);color:var(--text);white-space:nowrap;}
|
||||
.pager-text{flex:1 1 0;min-width:0;text-align:center;font-size:12px;white-space:nowrap;color:var(--muted);}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="app" id="app">
|
||||
<div class="top">
|
||||
<div class="bar">
|
||||
<div class="brand">永樂<b>視頻</b></div>
|
||||
<form class="search" id="searchForm">
|
||||
<input id="kw" class="focusable" type="text" placeholder="搜索影片、動漫、短劇…" autocomplete="off" />
|
||||
<button class="primary focusable" type="submit">搜索</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tabs" id="tabs"></div>
|
||||
</div>
|
||||
<div class="status" id="status">準備加載…</div>
|
||||
<main id="content"></main>
|
||||
</div>
|
||||
<div class="sheet" id="detailSheet">
|
||||
<div class="sheet-inner">
|
||||
<div class="detail-panel" id="detailPanel"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function(){
|
||||
'use strict';
|
||||
setTimeout(()=>{try{var el=document.getElementById('status');if(el&&el.textContent==='準備加載…')el.textContent='脚本已啟動,正在等待 SDK/加載首页…'}catch(e){}},100);
|
||||
const BASE='https://ylsp.tv';
|
||||
const CATS=[
|
||||
['首頁',BASE+'/'],
|
||||
['電影',BASE+'/vodshow/1-----------/'],
|
||||
['劇集',BASE+'/vodshow/2-----------/'],
|
||||
['綜藝',BASE+'/vodshow/3-----------/'],
|
||||
['動漫',BASE+'/vodshow/4-----------/'],
|
||||
['今日更新',BASE+'/label/new/'],
|
||||
['熱搜榜',BASE+'/label/hot/']
|
||||
];
|
||||
const UA='Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 Chrome/120 Mobile Safari/537.36';
|
||||
let fmReady=false,currentUrl=BASE+'/',lastFocus=null;
|
||||
let pageInfo={prev:'', next:'', current:1, total:1};
|
||||
const $=s=>document.querySelector(s), content=$('#content'), status=$('#status'), tabs=$('#tabs');
|
||||
function parsePager(html){
|
||||
const d = doc(html);
|
||||
const box =
|
||||
d.querySelector('#page') ||
|
||||
d.querySelector('.page') ||
|
||||
d.querySelector('.pagination');
|
||||
|
||||
if(!box){
|
||||
return {home:'',prev:'',next:'',last:'',current:1,total:1};
|
||||
}
|
||||
const get = (sel, attr='href') => {
|
||||
const el = box.querySelector(sel);
|
||||
return el ? abs(el.getAttribute(attr)) : '';
|
||||
};
|
||||
const currentEl =
|
||||
box.querySelector('.page-current') ||
|
||||
box.querySelector('.active');
|
||||
return {
|
||||
home: get('a[title="首页"]'),
|
||||
prev: get('a[title="上一页"]'),
|
||||
next: get('a[title="下一页"]'),
|
||||
last: get('a[title="尾页"]'),
|
||||
current: currentEl ? parseInt(currentEl.textContent) || 1 : 1,
|
||||
total: 1
|
||||
};
|
||||
}
|
||||
|
||||
function renderPager(){
|
||||
document.querySelectorAll('.pager-wrap').forEach(e => e.remove());
|
||||
const wrap = document.createElement('div');
|
||||
wrap.className = 'pager-wrap';
|
||||
wrap.innerHTML = `
|
||||
<button class="pager-btn" id="homePage">首页</button>
|
||||
<button class="pager-btn" id="prevPage">上一页</button>
|
||||
<span class="pager-text">第 ${pageInfo.current || 1} 页</span>
|
||||
<button class="pager-btn" id="nextPage">下一页</button>
|
||||
<button class="pager-btn" id="lastPage">尾页</button>`;
|
||||
|
||||
content.appendChild(wrap);
|
||||
const markActive = (btn) => {
|
||||
if(btn) btn.style.border = "1px solid #ffb86b";
|
||||
};
|
||||
|
||||
const home = wrap.querySelector('#homePage');
|
||||
const prev = wrap.querySelector('#prevPage');
|
||||
const next = wrap.querySelector('#nextPage');
|
||||
const last = wrap.querySelector('#lastPage');
|
||||
|
||||
home.onclick = ()=> pageInfo.home && loadPage(pageInfo.home);
|
||||
prev.onclick = ()=> pageInfo.prev && loadPage(pageInfo.prev);
|
||||
next.onclick = ()=> pageInfo.next && loadPage(pageInfo.next);
|
||||
last.onclick = ()=> pageInfo.last && loadPage(pageInfo.last);
|
||||
|
||||
if(!pageInfo.prev) prev.style.opacity = 0.4;
|
||||
if(!pageInfo.next) next.style.opacity = 0.4;
|
||||
if(pageInfo.home) markActive(home);
|
||||
if(pageInfo.last) markActive(last);
|
||||
}
|
||||
function markNative(){ if(window.fongmiBridge||window.fm||window.fongmi) document.documentElement.classList.add('fm-native'); }
|
||||
function waitFm(ms=1600){ markNative(); if(window.fm){fmReady=true;return Promise.resolve(window.fm)} return new Promise(r=>{let done=false;const ok=()=>{if(done)return;done=true;markNative();fmReady=!!window.fm;r(window.fm||null)};window.addEventListener('fmsdk',ok,{once:true});setTimeout(ok,ms);});}
|
||||
function abs(u){try{return new URL(u,BASE+'/').href}catch(e){return u||''}}
|
||||
function esc(s){return String(s||'').replace(/[&<>'"]/g,c=>({'&':'&','<':'<','>':'>',"'":''','"':'"'}[c]))}
|
||||
function clean(s){return String(s||'').replace(/\s+/g,' ').replace(/[]/g,'').trim()}
|
||||
function imgUrl(u){u=abs(u||''); if(!u) return ''; try{return window.fm&&fm.res?fm.res(u,{headers:{Referer:BASE+'/'}}):u}catch(e){return u}}
|
||||
async function reqText(url){
|
||||
url = abs(url);
|
||||
try{
|
||||
if(window.fm && fm.req){
|
||||
const r = await fm.req(url,{
|
||||
method:'GET',
|
||||
headers:{'User-Agent':UA,'Referer':BASE+'/'},
|
||||
responseType:'text',
|
||||
timeout:25,
|
||||
credentials:'include'
|
||||
});
|
||||
if(!r.ok) throw new Error(r.error || ('HTTP '+r.status));
|
||||
return r.body || '';
|
||||
}
|
||||
|
||||
const r = await fetch(url,{
|
||||
headers:{'Referer':BASE+'/'}
|
||||
});
|
||||
|
||||
if(!r.ok) throw new Error('HTTP '+r.status);
|
||||
|
||||
return await r.text();
|
||||
|
||||
}catch(e){
|
||||
setStatus('加載失敗:'+e.message);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
function doc(html){return new DOMParser().parseFromString(html,'text/html')}
|
||||
function cardFromA(a){const href=abs(a.getAttribute('href')||'');let img=a.querySelector('img');let image=img?(img.getAttribute('data-src')||img.getAttribute('data-original')||img.getAttribute('src')||''):'';let title=a.getAttribute('title')||img?.getAttribute('alt')||'';let text=clean(a.textContent); if(!title) title=text.replace(/^(4K)?(今日更新)?(更新至|已完结|HD|全).*?/,'').slice(0,24)||text;let remark=text.replace(title,'').slice(0,40);return {title:clean(title),remark:clean(remark),href,image:abs(image)}}
|
||||
function unique(arr){const m=new Map();arr.forEach(x=>{if(x&&x.href&&x.title&&!m.has(x.href))m.set(x.href,x)});return [...m.values()]}
|
||||
function parseList(html,keyword=''){
|
||||
const d=doc(html), sections=[];
|
||||
const badHost=el=>!!el.closest('.pop-list-body,.search-hot,.history,.footer,.head-more,.slide-time,.banner,.swiper,.large-swiper,.popup');
|
||||
const isGood=x=>x&&x.href&&x.title&&x.image&&!/^点击观看$/.test(x.title);
|
||||
let heads=[...d.querySelectorAll('.title-a,.module-title,.title')].map(h=>({el:h,title:clean(h.textContent).replace('更多','')})).filter(x=>x.title&&!/搜索|播放记录|搜索历史|热门搜索|更多/.test(x.title));
|
||||
for(const h of heads){
|
||||
let box=h.el.closest('.box-width,.module,.module-main,.layout-box,.box')||h.el.parentElement;
|
||||
let items=[];
|
||||
for(let i=0;i<4&&box;i++,box=box.parentElement){
|
||||
items=unique([...box.querySelectorAll('a[href*="/voddetail/"]')].filter(a=>!badHost(a)).map(cardFromA)).filter(isGood).slice(0,24);
|
||||
if(items.length>=3)break;
|
||||
}
|
||||
if(items.length && !/热榜RANKING|热榜|RANKING/i.test(h.title)){
|
||||
sections.push({title:h.title,items});
|
||||
}
|
||||
}
|
||||
if(!sections.length){
|
||||
let items=unique([...d.querySelectorAll('a[href*="/voddetail/"]')].filter(a=>!badHost(a)).map(cardFromA)).filter(isGood);
|
||||
if(keyword) items=items.filter(x=>x.title.includes(keyword)||x.remark.includes(keyword));
|
||||
items=items.slice(0,60);
|
||||
sections.push({title:keyword?('搜索:'+keyword):'',items});
|
||||
}
|
||||
return sections.filter(s=>s.items.length)
|
||||
}
|
||||
function parseDetail(html,url){
|
||||
const d=doc(html);
|
||||
const title=clean(d.querySelector('h1')?.textContent||d.title.replace(/《|》|免费观看.*$/g,'')).replace(/(电视剧|电影|动漫|综艺|短剧|連续剧|国产剧|动画)$/,'');
|
||||
let pic = d.querySelector('.module-item-pic img.lazyload');
|
||||
let image = pic?.getAttribute('data-original') || '';
|
||||
const raw=(d.body?.innerText||'').replace(/\r/g,'');
|
||||
const lines=raw.split('\n').map(x=>clean(x)).filter(Boolean);
|
||||
function pickLine(label){
|
||||
const labels=['类型','导演','演员','语言','連載','更新'];
|
||||
const re=new RegExp('^'+label+'\\s*[::]?\\s*(.*)$');
|
||||
const line=lines.find(x=>re.test(x));
|
||||
if(!line) return '';
|
||||
let v=(line.match(re)||[])[1]||'';
|
||||
v=v.replace(/^(:|:)/,'').trim();
|
||||
const others=labels.filter(x=>x!==label).join('|');
|
||||
v=v.replace(new RegExp('\\s*(?:'+others+')\\s*[::].*$'), '').trim();
|
||||
return v.length>120?v.slice(0,120)+'…':v;
|
||||
}
|
||||
const fields=[
|
||||
['类型',pickLine('类型')],
|
||||
['导演',pickLine('导演')],
|
||||
['演员',pickLine('演员')],
|
||||
['連載',pickLine('連載')],
|
||||
['更新',pickLine('更新')]
|
||||
].filter(x=>x[1]);
|
||||
if(!fields.length){
|
||||
const text=raw.replace(/\s+/g,' ');
|
||||
function pickAny(label,next){const re=new RegExp(label+'\\s*[::]\\s*(.*?)(?='+next+'|簡介|播放線路|$)');const m=text.match(re);return m?clean(m[1]).slice(0,120):''}
|
||||
const fs=[['类型',pickAny('类型','导演')],['导演',pickAny('导演','演员')],['演员',pickAny('演员','連載')],['連載',pickAny('連載','更新')],['更新',pickAny('更新','簡介')]].filter(x=>x[1]);
|
||||
fields.push(...fs);
|
||||
}
|
||||
let desc='暫無簡介';
|
||||
const descNode=d.querySelector('.module-info-introduction-content p,.module-info-introduction-content,.video-info-content,.vod_content,.detail-content,.content_desc');
|
||||
if(descNode) desc=clean(descNode.textContent).replace(/簡介|剧情介绍/g,'').replace(/\s+/g,' ').trim();
|
||||
if(!desc){
|
||||
let i=lines.findIndex(x=>x==='簡介'||x.startsWith('簡介')||x==='剧情介绍'||x.startsWith('剧情介绍'));
|
||||
if(i>=0){
|
||||
let arr=[];
|
||||
for(let j=i+1;j<lines.length;j++){
|
||||
if(/^(选集播放|播放線路|资源列表|热播推荐|相关推荐|⚠️|重要提醒|排序|切换节点|至臻|蓝光|自营|推荐\(\d+\))/.test(lines[j])) break;
|
||||
if(lines[j] && lines[j] !== '展開') arr.push(lines[j]);
|
||||
}
|
||||
desc=clean(arr.join(' ')).replace(/展開$/,'');
|
||||
}
|
||||
}
|
||||
if(!desc){
|
||||
let m=raw.match(/(?:簡介|剧情介绍)\s*([\s\S]*?)(?:选集播放|播放線路|资源列表|热播推荐|相关推荐|⚠️)/);
|
||||
desc=m?clean(m[1]).replace(/展開$/,''):'暫無簡介';
|
||||
}
|
||||
desc=desc.replace(/\s*展開\s*$/,'');
|
||||
const fullDesc = desc && desc.trim() ? desc : '暫無簡介';
|
||||
|
||||
let shortDesc = fullDesc;
|
||||
const NEED_TRIM = 120;
|
||||
|
||||
if (fullDesc.length > NEED_TRIM) {
|
||||
shortDesc = fullDesc.slice(0, NEED_TRIM) + '…';
|
||||
}
|
||||
let lineNames=[...d.querySelectorAll('.module-tab-item.tab-item')].map(x=>clean(x.getAttribute('data-dropdown-value')||x.querySelector('span')?.textContent||x.textContent.replace(/\d+$/,''))).filter(Boolean);
|
||||
if(!lineNames.length) lineNames=[...raw.matchAll(new RegExp('([^\s\n()]{2,12})\((\d+)\)','g'))].map(m=>clean(m[1])).filter(x=>!/提醒|搜索|播放|记录/.test(x));
|
||||
lineNames=[...new Set(lineNames)];
|
||||
function epNo(ep){let m=(ep.href||'').match(/\/(?:vodplay|vplay|play)\/\d+-\d+-(\d+)\//); if(m) return parseInt(m[1],10)||0; let t=(ep.title||'').match(/(\d+)/); return t?parseInt(t[1],10):0;}
|
||||
let linesGroup=[];
|
||||
const panels=[...d.querySelectorAll('.module-list.sort-list.tab-list.his-tab-list')];
|
||||
if(panels.length){
|
||||
panels.forEach((pl,i)=>{let arr=unique([...pl.querySelectorAll('a.module-play-list-link,a[href*="/vodplay/"],a[href*="/vplay/"],a[href*="/play/"]')].map(a=>({title:clean(a.querySelector('span')?.textContent||a.textContent)||'播放',href:abs(a.getAttribute('href')),remark:'',image:''}))).sort((a,b)=>epNo(a)-epNo(b));if(arr.length)linesGroup.push({sid:String(i+1),name:lineNames[i]||('線路'+(i+1)),eps:arr})});
|
||||
}else{
|
||||
const groups={}, lineOrder=[];
|
||||
let eps0=unique([...d.querySelectorAll('a.module-play-list-link,a[href*="/vodplay/"],a[href*="/vplay/"],a[href*="/play/"]')].map(a=>({title:clean(a.querySelector('span')?.textContent||a.textContent)||'播放',href:abs(a.getAttribute('href')),remark:'',image:''}))).slice(0,320);
|
||||
eps0.forEach(ep=>{let m=ep.href.match(/\/(?:vodplay|vplay|play)\/\d+-(\d+)-\d+\//);let sid=m?m[1]:'1';if(!groups[sid]){groups[sid]=[];lineOrder.push(sid)}groups[sid].push(ep)});
|
||||
lineOrder.forEach(sid=>groups[sid].sort((a,b)=>epNo(a)-epNo(b)));
|
||||
linesGroup=lineOrder.map((sid,i)=>({sid,name:lineNames[i]||('線路'+sid),eps:groups[sid]}));
|
||||
}
|
||||
let eps=linesGroup[0]?.eps||[];
|
||||
return {title,image:abs(image),fields,desc:shortDesc,fullDesc,eps,lines:linesGroup,url}
|
||||
}
|
||||
function parsePlay(html, playPage){
|
||||
let p = null;
|
||||
|
||||
const m = html.match(/var\s+player_aaaa\s*=\s*({[\s\S]*?})\s*<\/script>/);
|
||||
if (m) {
|
||||
try {
|
||||
p = JSON.parse(m[1].replace(/\\\//g,'/'));
|
||||
} catch(e){}
|
||||
}
|
||||
|
||||
const d = doc(html);
|
||||
|
||||
let iframe = d.querySelector('iframe[src]');
|
||||
let src = iframe ? abs(iframe.getAttribute('src')) : '';
|
||||
|
||||
function unwrap(u){
|
||||
if(!u) return '';
|
||||
try{
|
||||
if(u.includes('webResource?url=')){
|
||||
const x = new URL(u);
|
||||
return decodeURIComponent(x.searchParams.get('url')||'');
|
||||
}
|
||||
}catch(e){}
|
||||
return u;
|
||||
}
|
||||
|
||||
src = unwrap(src);
|
||||
let url = unwrap(p?.url || '');
|
||||
return {
|
||||
src,
|
||||
url,
|
||||
title: p?.vod_data?.vod_name || document.title || '播放',
|
||||
page: playPage
|
||||
};
|
||||
}
|
||||
function setStatus(t){status.textContent=t||''}
|
||||
function renderTabs(active){tabs.innerHTML=CATS.map(([n,p])=>`<button class="tab focusable ${abs(p)===abs(active)?'active':''}" data-url="${esc(abs(p))}">${esc(n)}</button>`).join('');tabs.querySelectorAll('.tab').forEach(b=>b.onclick=()=>loadPage(b.dataset.url))}
|
||||
function renderSections(sections){content.innerHTML=sections.map(sec=>`<section class="section"><div class="section-title"><h2>${esc(sec.title)}</h2></div><div class="grid">${sec.items.map(it=>`<article class="card focusable" tabindex="0" data-href="${esc(it.href)}"><div class="poster">${it.image?`<img loading="lazy" referrerpolicy="no-referrer" src="${esc(imgUrl(it.image))}" alt="${esc(it.title)}">`:`<div class="noimg">枫</div>`}</div><div class="meta"><div class="name">${esc(it.title)}</div><div class="remark">${esc(it.remark||'点击查看詳情')}</div></div></article>`).join('')}</div></section>`).join('');content.querySelectorAll('.card').forEach(c=>{c.onclick=()=>openDetail(c.dataset.href);c.onkeydown=e=>{if(e.key==='Enter')c.click()}})}
|
||||
async function loadPage(url){
|
||||
currentUrl=abs(url);
|
||||
|
||||
renderTabs(currentUrl);
|
||||
content.innerHTML='<div class="empty loading">正在加載內容…</div>';
|
||||
|
||||
try{
|
||||
const html=await reqText(currentUrl);
|
||||
|
||||
pageInfo=parsePager(html);
|
||||
|
||||
const sections=parseList(html);
|
||||
|
||||
renderSections(sections);
|
||||
|
||||
if (pageInfo && (pageInfo.prev || pageInfo.next || pageInfo.home || pageInfo.last)) {
|
||||
renderPager();
|
||||
}
|
||||
|
||||
setStatus('');
|
||||
|
||||
restoreFocus();
|
||||
|
||||
}catch(e){
|
||||
content.innerHTML='<div class="empty">加載失敗:'+esc(e.message)+'</div>';
|
||||
}
|
||||
}
|
||||
async function search(q){
|
||||
q=clean(q);if(!q)return;
|
||||
renderTabs(BASE+'/');
|
||||
history.pushState({route:'search',q},'', '#search='+encodeURIComponent(q));
|
||||
content.innerHTML='<div class="empty loading">搜索中…</div>';
|
||||
try{
|
||||
const urls=[
|
||||
BASE+'/vodsearch/'+encodeURIComponent(q)+'-------------/'
|
||||
];
|
||||
let sections=[];
|
||||
for(const url of urls){
|
||||
const html=await reqText(url);
|
||||
pageInfo = parsePager(html);
|
||||
sections=parseList(html,q);
|
||||
if(sections.length&§ions[0].items.length) break;
|
||||
}
|
||||
if(!sections.length||!sections[0].items.length){
|
||||
content.innerHTML='<div class="empty">没有找到相关结果</div>';
|
||||
setStatus('無搜索结果:'+q);
|
||||
return;
|
||||
}
|
||||
sections[0].title = '搜索:' + q;
|
||||
renderSections(sections);
|
||||
|
||||
if (pageInfo && (pageInfo.prev || pageInfo.next || pageInfo.home || pageInfo.last)) {
|
||||
renderPager();
|
||||
}
|
||||
}catch(e){
|
||||
content.innerHTML='<div class="empty">搜索失敗,請換個關鍵詞重試。</div>';
|
||||
setStatus('搜索失敗:'+e.message);
|
||||
}
|
||||
}
|
||||
async function openDetail(url){
|
||||
lastFocus=document.activeElement;
|
||||
document.body.classList.add('detail-active');
|
||||
$('#detailSheet').classList.add('active');
|
||||
|
||||
$('#detailPanel').innerHTML='<div class="empty loading">正在讀取詳情…</div>';
|
||||
|
||||
history.pushState({route:'detail',url},'', '#detail='+encodeURIComponent(url));
|
||||
|
||||
try{
|
||||
const html=await reqText(url);
|
||||
const d=parseDetail(html,url);
|
||||
let firstLine=d.lines[0]||{sid:'1',name:'默认線路',eps:d.eps};
|
||||
|
||||
function trimFieldValue(label,value){
|
||||
const labels=['类型','导演','演员','语言','連載','更新'].filter(x=>x!==label).join('|');
|
||||
return String(value||'').replace(new RegExp('\\s*(?:'+labels+')\\s*[::].*$'),'').trim();
|
||||
}
|
||||
|
||||
const shownFields=d.fields
|
||||
.filter(x=>x[0]!=='更新')
|
||||
.map(x=>[x[0],trimFieldValue(x[0],x[1])])
|
||||
.filter(x=>x[1]);
|
||||
|
||||
$('#detailPanel').innerHTML=`
|
||||
<button class="focusable" id="closeDetail">返回</button>
|
||||
|
||||
<div class="detail-head">
|
||||
<img class="detail-pic" referrerpolicy="no-referrer"
|
||||
src="${esc(imgUrl(d.image))}"
|
||||
onerror="this.style.display='none'">
|
||||
|
||||
<div class="detail-side">
|
||||
<div class="detail-title">${esc(d.title)}</div>
|
||||
<div class="detail-info">
|
||||
${shownFields.map(x=>`
|
||||
<div><b>${esc(x[0])}:</b>${esc(x[1])}</div>
|
||||
`).join('')||''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="intro-pill">簡介</div>
|
||||
|
||||
<p class="desc" id="descText">${esc(d.shortDesc || d.desc || '暫無簡介')}</p>
|
||||
|
||||
<button class="line-tab" id="toggleDesc">展開</button>
|
||||
|
||||
<div class="play-title">播放線路</div>
|
||||
|
||||
<div class="line-tabs">
|
||||
${(d.lines.length?d.lines:[firstLine]).map((ln,i)=>`
|
||||
<button class="line-tab focusable ${i===0?'active':''}" data-sid="${esc(ln.sid)}">
|
||||
${esc(ln.name)}(${ln.eps.length})
|
||||
</button>
|
||||
`).join('')}
|
||||
</div>
|
||||
|
||||
<div class="episodes" id="episodeBox"></div>
|
||||
`;
|
||||
|
||||
function renderLine(sid){
|
||||
const ln=d.lines.find(x=>x.sid==sid)||firstLine;
|
||||
|
||||
$('#episodeBox').innerHTML=ln.eps.map(ep=>`
|
||||
<button class="ep focusable" data-href="${esc(ep.href)}">
|
||||
${esc(ep.title)}
|
||||
</button>
|
||||
`).join('')||'<div class="empty">暫無分集</div>';
|
||||
|
||||
$('#episodeBox').querySelectorAll('.ep')
|
||||
.forEach(b=>b.onclick=()=>playEpisode(b.dataset.href,d.title+' '+b.textContent));
|
||||
}
|
||||
$('#closeDetail').onclick=closeDetail;
|
||||
$('#detailPanel')
|
||||
.querySelectorAll('.line-tab[data-sid]')
|
||||
.forEach(b=>{
|
||||
b.onclick=()=>{
|
||||
$('#detailPanel')
|
||||
.querySelectorAll('.line-tab[data-sid]')
|
||||
.forEach(x=>x.classList.remove('active'));
|
||||
|
||||
b.classList.add('active');
|
||||
renderLine(b.dataset.sid);
|
||||
};
|
||||
});
|
||||
|
||||
const toggleBtn = document.getElementById('toggleDesc');
|
||||
const descEl = document.getElementById('descText');
|
||||
|
||||
let expanded = false;
|
||||
const full = d.fullDesc || '';
|
||||
const short = (d.fullDesc && d.fullDesc.length > 66)
|
||||
? d.fullDesc.slice(0, 66) + '…'
|
||||
: d.fullDesc || '';
|
||||
|
||||
if (toggleBtn && descEl) {
|
||||
descEl.textContent = short;
|
||||
toggleBtn.textContent = '展開';
|
||||
|
||||
toggleBtn.onclick = () => {
|
||||
expanded = !expanded;
|
||||
|
||||
if (expanded) {
|
||||
descEl.textContent = full;
|
||||
toggleBtn.textContent = '收起';
|
||||
} else {
|
||||
descEl.textContent = short;
|
||||
toggleBtn.textContent = '展開';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
renderLine(firstLine.sid);
|
||||
$('#closeDetail').focus({preventScroll:true});
|
||||
|
||||
}catch(e){
|
||||
$('#detailPanel').innerHTML=
|
||||
'<button class="focusable" onclick="window.__fyClose()">返回</button>'+
|
||||
'<div class="empty">詳情加載失敗:'+esc(e.message)+'</div>';
|
||||
}
|
||||
}
|
||||
async function playEpisode(url, title){
|
||||
setStatus('正在解析播放…');
|
||||
|
||||
const playUrl = abs(url);
|
||||
|
||||
try{
|
||||
const html = await reqText(playUrl);
|
||||
const pp = parsePlay(html, playUrl);
|
||||
|
||||
let u = pp.url || pp.src;
|
||||
|
||||
if(!u){
|
||||
location.href = playUrl;
|
||||
return;
|
||||
}
|
||||
|
||||
if(window.fm && fm.play){
|
||||
await fm.play(u, title);
|
||||
setStatus('播放中:' + title);
|
||||
return;
|
||||
}
|
||||
|
||||
location.href = u;
|
||||
|
||||
}catch(e){
|
||||
console.warn(e);
|
||||
location.href = playUrl;
|
||||
}
|
||||
}
|
||||
function closeDetail(){document.body.classList.remove('detail-active');$('#detailSheet').classList.remove('active');if(location.hash.startsWith('#detail=')) history.back(); if(lastFocus&&lastFocus.focus) setTimeout(()=>lastFocus.focus({preventScroll:true}),50)}window.__wbClose=closeDetail;
|
||||
async function nativeSearch(q){q=q||clean($('#kw').value);if(!q)return;if(window.fm&&fm.search){try{await fm.search(q,{direct:true});return}catch(e){}}search(q)}
|
||||
function restoreFocus(){setTimeout(()=>{const el=document.querySelector('.card')||document.querySelector('.tab.active'); if(el) el.focus({preventScroll:true})},30)}
|
||||
function handleKeys(e){if(e.key==='Escape'||e.key==='Backspace'){if($('#detailSheet').classList.contains('active')){e.preventDefault();closeDetail();return}}if(e.key==='Enter'&&document.activeElement?.classList.contains('focusable')&&document.activeElement.tagName!=='INPUT'){document.activeElement.click();}}
|
||||
function boot(){setStatus('正在初始化…');renderTabs(BASE+'/');$('#searchForm').onsubmit=e=>{e.preventDefault();search($('#kw').value)};const ns=$('#nativeSearch'); if(ns) ns.onclick=()=>nativeSearch();document.addEventListener('keydown',handleKeys,true);window.addEventListener('popstate',()=>{if($('#detailSheet').classList.contains('active')){document.body.classList.remove('detail-active');$('#detailSheet').classList.remove('active')}else loadPage(currentUrl)});loadPage(BASE+'/')}
|
||||
setTimeout(()=>{try{if(status&&status.textContent==='準備加載…')status.textContent='正在初始化 WebHome…'}catch(e){}},800);
|
||||
waitFm().then(()=>{try{boot()}catch(e){status.textContent='啟動失敗:'+e.message;console.error(e)}});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Ссылка в новой задаче
Block a user