@lzwme/m3u8-dl 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.MD +445 -71
- package/README.zh-CN.md +580 -0
- package/cjs/cli.js +37 -24
- package/cjs/i18n/locales/en.d.ts +108 -0
- package/cjs/i18n/locales/en.js +109 -0
- package/cjs/i18n/locales/zh-CN.d.ts +108 -0
- package/cjs/i18n/locales/zh-CN.js +109 -0
- package/cjs/index.d.ts +1 -1
- package/cjs/index.js +1 -1
- package/cjs/lib/file-download.js +8 -5
- package/cjs/lib/format-options.js +27 -4
- package/cjs/lib/getM3u8Urls.d.ts +6 -0
- package/cjs/lib/getM3u8Urls.js +45 -27
- package/cjs/lib/i18n.d.ts +27 -0
- package/cjs/lib/i18n.js +108 -0
- package/cjs/lib/m3u8-convert.js +2 -7
- package/cjs/lib/m3u8-download.js +36 -15
- package/cjs/lib/utils.js +4 -4
- package/cjs/server/download-server.d.ts +1 -0
- package/cjs/server/download-server.js +112 -39
- package/cjs/types/index.d.ts +1 -1
- package/cjs/types/index.js +1 -1
- package/cjs/types/m3u8.d.ts +4 -2
- package/client/assets/main-ChJ1yjNN.css +1 -0
- package/client/assets/main-DZTEqg-V.js +29 -0
- package/client/index.html +5 -1145
- package/client/m3u8-capture.user.js +94 -0
- package/client/play.html +223 -16
- package/package.json +34 -21
- package/client/style.css +0 -137
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// ==UserScript==
|
|
2
|
+
// @name [M3U8-DL]媒体链接抓取器
|
|
3
|
+
// @namespace https://github.com/lzwme/m3u8-dl
|
|
4
|
+
// @homepage https://m3u8-player.lzw.me/download.html
|
|
5
|
+
// @supportURL https://github.com/lzwme/m3u8-dl/issues
|
|
6
|
+
// @icon https://gh-proxy.org/raw.githubusercontent.com/lzwme/m3u8-dl/refs/heads/main/packages/frontend/public/logo.png
|
|
7
|
+
// @version 1.0.0
|
|
8
|
+
// @description 自动抓取网页中的多种媒体链接(m3u8、mp4、mkv、avi、mov、音频等),支持可配置的媒体类型,支持跳转到 m3u8-dl webui 下载
|
|
9
|
+
// @author lzw
|
|
10
|
+
// @updateURL https://gh-proxy.org/raw.githubusercontent.com/lzwme/m3u8-dl/refs/heads/main/client/m3u8-capture.user.js
|
|
11
|
+
// @downloadURL https://raw.githubusercontent.com/lzwme/m3u8-dl/refs/heads/main/client/m3u8-capture.user.js
|
|
12
|
+
// @match *://*/*
|
|
13
|
+
// @grant GM_addElement
|
|
14
|
+
// @grant GM_setValue
|
|
15
|
+
// @grant GM_getValue
|
|
16
|
+
// @grant GM_getResourceText
|
|
17
|
+
// @grant GM_xmlhttpRequest
|
|
18
|
+
// @grant unsafeWindow
|
|
19
|
+
// @resource SwalJS https://s4.zstatic.net/ajax/libs/sweetalert2/11.16.1/sweetalert2.min.js
|
|
20
|
+
// @resource SwalCSS https://s4.zstatic.net/ajax/libs/sweetalert2/11.16.1/sweetalert2.css
|
|
21
|
+
// @resource TailwindCSS https://s4.zstatic.net/ajax/libs/tailwindcss/2.2.19/tailwind.min.css
|
|
22
|
+
// @run-at document-idle
|
|
23
|
+
// ==/UserScript==
|
|
24
|
+
|
|
25
|
+
(function(){"use strict";const j="m3u8_capture_webui_url",K="m3u8_capture_exclude_urls",J="m3u8_capture_panel_pos",Q="m3u8_capture_panel_visible",Z="m3u8_capture_media_ext_list",ut=["m3u8","mp4","mkv","avi","mov","wmv","flv","webm","m4v","m3u","m4a","aac","flac","ape","mp3","wav","ogg","wma"];function V(){return GM_getValue(j,"http://localhost:6600").replace(/\/$/,"")}function tt(){return GM_getValue(K,"")}function ft(t){GM_setValue(K,t)}function U(){const t=GM_getValue(Z,[]);return t&&Array.isArray(t)&&t.length>0?t:[...ut]}function pt(t){if(Array.isArray(t)&&t.length>0){const e=t.map(n=>n.trim().toLowerCase()).filter(n=>n&&/^[a-z0-9]+$/i.test(n));return GM_setValue(Z,e),e}return null}function mt(){return GM_getValue(J,null)}function xt(t){GM_setValue(J,t)}function wt(){return GM_getValue(Q,!1)}function et(t){GM_setValue(Q,t)}function W(t,e=document.head,n="css"){const o=t.length<50?GM_getResourceText(t):t;return Promise.resolve(GM_addElement(e,n==="css"?"style":"script",{type:n==="css"?"text/css":"text/javascript",textContent:o}))}function gt(){const t=U();return new RegExp(`\\.(${t.join("|")})(\\?|$|#)`,"i")}function k(t){const e=window.location.href;if(e.startsWith(V()))return!0;const n=tt();if(!n||!n.trim())return!1;const o=n.split(`
|
|
26
|
+
`).map(i=>i.trim()).filter(i=>i);for(const i of o)try{if(e.includes(i)||i.startsWith("/")&&i.endsWith("/")&&new RegExp(i.slice(1,-1)).test(e))return!0}catch(a){console.warn("[M3U8 Capture] 排除规则格式错误:",i,a)}return!1}function nt(t){return new Promise((e,n)=>{navigator.clipboard?.writeText?navigator.clipboard.writeText(t).then(()=>e(!0)).catch(()=>{ot(t)?e(!0):n(new Error("复制失败"))}):ot(t)?e(!0):n(new Error("复制失败"))})}function ot(t){try{const e=document.createElement("textarea");e.value=t,e.style.position="fixed",e.style.opacity="0",e.style.left="-9999px",document.body.appendChild(e),e.select(),e.setSelectionRange(0,t.length);const n=document.execCommand("copy");return document.body.removeChild(e),n}catch(e){return console.error("[M3U8 Capture] fallbackCopy failed:",e),!1}}function ht(t){try{const e=new URL(t);return`${e.protocol}//${e.host}${e.pathname}`}catch{return t}}function O(t){return"touches"in t&&t.touches.length>0?{x:t.touches[0].clientX,y:t.touches[0].clientY}:"changedTouches"in t&&t.changedTouches.length>0?{x:t.changedTouches[0].clientX,y:t.changedTouches[0].clientY}:{x:t.clientX,y:t.clientY}}function y(t){if(!t||typeof t!="string"||!t.startsWith("http"))return!1;const e=t.toLowerCase();return!!(gt().test(e)||e.includes(".m3u8"))}function it(t){if(!t||typeof t!="string")return"media";const e=t.toLowerCase(),n=U();for(const o of n)if(new RegExp(`\\.${o}(\\?|$|#)`,"i").test(e))return o;return/m3u8/i.test(e)?"m3u8":"media"}function q(t=document){let e="";const n=["h1.title","h2.title","h1","h2"];for(const o of n){const i=t.querySelector(o);if(i?.textContent){e=i.textContent.trim();break}}if(e||(e=(t.title||"").split(/[-|_]/)[0].trim()),!e&&window.top!==window.self)try{e=q(window.top?.document)}catch{}return e}function yt(t){if(!t||typeof t!="string")return null;try{const e=new URL(t);for(const[n,o]of e.searchParams.entries()){if(!o)continue;const i=decodeURIComponent(o);if(y(i))return i}}catch{}return null}function bt(){const t=XMLHttpRequest.prototype.open;XMLHttpRequest.prototype.open=function(n,o,i,a,s){return y(o.toString())&&this.addEventListener("load",function(){this.status>=200&&this.status<300&&h(o.toString())}),t.call(this,n,o,i??!0,a,s)};const e=window.fetch;window.fetch=function(n,o){const i=typeof n=="string"?n:n&&typeof n=="object"&&"url"in n?n.url:"";return y(i)?e.call(this,n,o).then(a=>(a.ok&&h(i),a)):e.call(this,n,o)}}function vt(){if(typeof PerformanceObserver>"u")return;const t=new PerformanceObserver(e=>{for(const n of e.getEntries())n.name&&y(n.name)&&h(n.name)});try{t.observe({entryTypes:["resource"]})}catch{console.log("[M3U8 Capture] PerformanceObserver not supported")}}function A(){if(document.querySelectorAll("video").forEach(t=>{t.src&&y(t.src)&&h(t.src,t.getAttribute("title")||""),t.querySelectorAll("source").forEach(e=>{e.src&&y(e.src)&&h(e.src,t.getAttribute("title")||"")})}),document.querySelectorAll("a[href]").forEach(t=>{const e=t.getAttribute("href");if(e&&y(e))try{const n=new URL(e,window.location.href).href;h(n,t.textContent?.trim()||"")}catch{}}),document.body){const t=document.body.innerText||"",n=U().join("|"),o=new RegExp(`https?:\\/\\/[^\\s"'<>]+\\.(${n})(\\?[^\\s"'<>]*)?`,"gi");let i;for(;(i=o.exec(t))!==null;)h(i[0])}}async function Et(){let t=document.body;const e=o=>{t=o},n=()=>t;unsafeWindow.SetSwalTarget=e,window.SetSwalTarget=e,unsafeWindow.GetSwalTarget=n,window.GetSwalTarget=n;try{const o=GM_getResourceText("SwalJS").replace(/document\.body/g,"GetSwalTarget()"),i=()=>W(o,document.head||document.documentElement,"script").then(()=>{const a=window.Swal||window.Sweetalert2||unsafeWindow.Swal||unsafeWindow.Sweetalert2;a&&(window.Swal=a,unsafeWindow.Swal=a)}).catch(a=>{console.error("[M3U8 Capture] Failed to add SweetAlert2 script:",a)});document.head||document.documentElement?await i():document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>i()):setTimeout(i,50)}catch(o){console.error("[M3U8 Capture] Failed to load SweetAlert2:",o)}}function St(t,e){W(GM_getResourceText("SwalCSS").replace(/:root *{/,`#${e.id} {`).replace(/body/g,""),t,"css"),setTimeout(()=>{const n=window.Swal;typeof window.SetSwalTarget=="function"&&n&&(window.SetSwalTarget(e),window.Swal=n.mixin({target:e}))},500)}const m=window.top&&window.top!==window.self;let S=null,b=null,l=null,r=null,p=null,$=wt(),B=!1;const P={x:0,y:0};let M=!1;const R={x:0,y:0};let z={x:0,y:0},C=!1,L=null;const g={x:0,y:0};let x;function Mt(t){x=t}function rt(){if(S)return b;S=document.createElement("div"),S.id="m3u8-capture-shadow-host",S.style.cssText="position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 9999;",document.body.appendChild(S),b=S.attachShadow({mode:"open"}),W("TailwindCSS",b,"css");const t=document.createElement("div");t.id="m3u8-capture-swal-container",b.appendChild(t);const e=document.createElement("style");return e.textContent=[":host { all: initial; font-family: system-ui, -apple-system, sans-serif; }","* { box-sizing: border-box; }",".hidden { display: none !important; }",`#${t.id},`,`#${t.id} * { pointer-events: auto !important; }`].join(`
|
|
27
|
+
`),b.appendChild(e),Et().then(()=>{St(b,t)}),b}function at(){if(r||m)return;const t=rt();if(!t)return;r=document.createElement("div"),r.id="m3u8-capture-toggle-btn",r.style.cssText="position: fixed; bottom: 40px; right: 20px; width: 50px; height: 50px; pointer-events: auto; z-index: 999998; will-change: transform;",r.className=`fixed bottom-10 right-5 w-[50px] h-[50px] bg-blue-500 rounded-full flex items-center justify-center cursor-move shadow-lg text-2xl transition-all duration-200 hover:scale-110 hover:shadow-xl select-none touch-none ${$?"hidden":"flex"}`;const e=document.createElement("span");e.textContent="🎬",r.appendChild(e),p=document.createElement("span"),p.id="m3u8-capture-toggle-badge",p.style.cssText="position: absolute; top: -4px; right: -4px; min-width: 18px; height: 18px; background: #ef4444; color: white; border-radius: 9px; font-size: 11px; font-weight: bold; display: flex; align-items: center; justify-content: center; padding: 0 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.2); line-height: 1;",p.textContent="0",p.classList.add("hidden"),r.appendChild(p);const n=o=>{if(!r)return;M=!0,C=!1;const i=O(o);z={x:i.x,y:i.y};const a=r.getBoundingClientRect();R.x=i.x-a.left,R.y=i.y-a.top,g.x=a.left,g.y=a.top;const s=window.getComputedStyle(r);s.transform&&s.transform!=="none"&&(r.style.transform="none",r.style.left=`${a.left}px`,r.style.top=`${a.top}px`,r.style.right="auto",r.style.bottom="auto"),r.style.cursor="move",r.style.transition="none",o.preventDefault(),o.stopPropagation()};r.addEventListener("mousedown",n),r.addEventListener("touchstart",n,{passive:!1}),r.addEventListener("click",()=>{C||st()}),r.addEventListener("touchend",o=>{M&&!C&&(o.preventDefault(),o.stopPropagation(),st())},{passive:!1}),t.appendChild(r),F()}function F(){if(!p||m||!x)return;const t=x.size;t>0?(p.textContent=t>99?"99+":t.toString(),p.classList.remove("hidden")):p.classList.add("hidden")}function st(){m||!ct()||($=!0,et(!0),l&&(l.style.display="flex"),r&&r.classList.add("hidden"))}function H(){m||($=!1,et(!1),l&&(l.style.display="none"),r?r.classList.remove("hidden"):at(),F())}function lt(){const t=window.Swal;t&&t.fire({title:"确认清空",text:"确定要清空所有媒体链接吗?",icon:"warning",showCancelButton:!0,confirmButtonText:"确定",cancelButtonText:"取消",confirmButtonColor:"#3b82f6"}).then(e=>{e.isConfirmed&&x&&(x.clear(),N())})}function ct(){if(!document.body||m)return null;if(l)return l;const t=rt();if(!t)return null;const e=document.createElement("div");e.id="m3u8-capture-panel";const n=mt(),o=n&&window.innerWidth>768?{left:`${Math.max(0,Math.min(n.x,window.innerWidth-450))}px`,top:`${Math.max(0,Math.min(n.y,window.innerHeight-350))}px`,right:"auto"}:{right:"20px",top:"20px"};e.className="fixed w-[420px] max-w-[90vw] max-h-[85vh] bg-white border-2 border-blue-500 rounded-xl shadow-2xl font-sans flex flex-col",e.style.cssText=`
|
|
28
|
+
position: fixed;
|
|
29
|
+
width: 420px;
|
|
30
|
+
max-width: 90vw;
|
|
31
|
+
max-height: 85vh;
|
|
32
|
+
pointer-events: auto;
|
|
33
|
+
z-index: 1059;
|
|
34
|
+
${o.left?`left: ${o.left};`:""}
|
|
35
|
+
${o.top?`top: ${o.top};`:""}
|
|
36
|
+
${`right: ${o.right};`}
|
|
37
|
+
display: ${$?"flex":"none"};
|
|
38
|
+
`,e.innerHTML=`
|
|
39
|
+
<div id="m3u8-capture-header" class="bg-gradient-to-br from-blue-500 to-blue-600 text-white px-4 py-3.5 rounded-t-lg flex justify-between items-center cursor-move select-none touch-none">
|
|
40
|
+
<div class="font-semibold text-[15px] flex items-center gap-2">
|
|
41
|
+
<span>🎬</span>
|
|
42
|
+
<span>媒体链接抓取器</span>
|
|
43
|
+
<span id="m3u8-capture-count" class="bg-white bg-opacity-25 px-2 py-0.5 rounded-xl text-xs font-medium">0</span>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="flex gap-1.5">
|
|
46
|
+
<button id="m3u8-capture-settings" class="bg-white bg-opacity-20 border-none text-white px-2.5 py-1.5 rounded-md cursor-pointer text-xs transition-colors duration-200 hover:bg-opacity-30 active:bg-opacity-40 touch-manipulation" title="设置">⚙️</button>
|
|
47
|
+
<button id="m3u8-capture-toggle" class="bg-white bg-opacity-20 border-none text-white px-2.5 py-1.5 rounded-md cursor-pointer text-xs transition-colors duration-200 hover:bg-opacity-30 active:bg-opacity-40 touch-manipulation" title="隐藏">−</button>
|
|
48
|
+
<button id="m3u8-capture-clear" class="bg-white bg-opacity-20 border-none text-white px-2.5 py-1.5 rounded-md cursor-pointer text-xs transition-colors duration-200 hover:bg-opacity-30 active:bg-opacity-40 touch-manipulation" title="清空">🗑️</button>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
<div id="m3u8-capture-content" class="p-3 overflow-y-auto flex-1 bg-gray-50">
|
|
52
|
+
<div id="m3u8-capture-list" class="flex flex-col gap-2.5"></div>
|
|
53
|
+
<div id="m3u8-capture-empty" class="text-center text-gray-400 py-10 px-5 hidden">
|
|
54
|
+
<div class="text-5xl mb-3">📹</div>
|
|
55
|
+
<div class="text-sm">暂无媒体链接</div>
|
|
56
|
+
<div class="text-xs text-gray-300 mt-2">浏览网页时会自动抓取</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
`,t.appendChild(e),l=e;const i=e.querySelector("#m3u8-capture-header");if(!i)return l;const a=u=>{const f=u.target;if(f.tagName==="BUTTON"||f.closest("button"))return;B=!0;const T=O(u),_=e.getBoundingClientRect();P.x=T.x-_.left,P.y=T.y-_.top,e.style.cursor="move",u.preventDefault(),u.stopPropagation()};i.addEventListener("mousedown",a),i.addEventListener("touchstart",a,{passive:!1});const s=u=>{const f=O(u);if(B&&l){u.preventDefault();const T=f.x-P.x,_=f.y-P.y,X=window.innerWidth-e.offsetWidth,Y=window.innerHeight-e.offsetHeight,D=Math.max(0,Math.min(T,X)),G=Math.max(0,Math.min(_,Y));e.style.left=`${D}px`,e.style.top=`${G}px`,e.style.right="auto",xt({x:D,y:G})}if(M&&r){u.preventDefault();const T=f.x-R.x,_=f.y-R.y,X=window.innerWidth-r.offsetWidth,Y=window.innerHeight-r.offsetHeight,D=Math.max(0,Math.min(T,X)),G=Math.max(0,Math.min(_,Y));g.x=D,g.y=G,L||(L=requestAnimationFrame(()=>{r&&M&&(r.style.transform=`translate(${g.x}px, ${g.y}px)`,r.style.left="0",r.style.top="0",r.style.right="auto",r.style.bottom="auto"),L=null})),Math.sqrt((f.x-z.x)**2+(f.y-z.y)**2)>5&&(C=!0)}},d=u=>{B&&(B=!1,l&&(l.style.cursor="default"),u.preventDefault()),M&&(M=!1,L&&(cancelAnimationFrame(L),L=null),r&&(r.style.cursor="move",r.style.transition="",C&&(r.style.transform=`translate(${g.x}px, ${g.y}px)`,r.style.left="0",r.style.top="0",r.style.right="auto",r.style.bottom="auto")),C&&u.preventDefault())};document.addEventListener("mousemove",s),document.addEventListener("mouseup",d),document.addEventListener("touchmove",s,{passive:!1}),document.addEventListener("touchend",d,{passive:!1}),document.addEventListener("touchcancel",d,{passive:!1});const c=u=>f=>{f.preventDefault(),f.stopPropagation(),u()},v=e.querySelector("#m3u8-capture-toggle");v&&(v.addEventListener("click",c(()=>H())),v.addEventListener("touchend",c(()=>H()),{passive:!1}));const w=e.querySelector("#m3u8-capture-clear");w&&(w.addEventListener("click",c(()=>lt())),w.addEventListener("touchend",c(()=>lt()),{passive:!1}));const E=e.querySelector("#m3u8-capture-settings");return E&&(E.addEventListener("click",c(()=>dt())),E.addEventListener("touchend",c(()=>dt()),{passive:!1})),!$&&!r&&at(),l}function dt(){const t=window.Swal;if(!t)return;const e=tt(),n=U();t.fire({title:"设置",html:`
|
|
60
|
+
<div class="text-left">
|
|
61
|
+
<label class="block text-sm font-medium text-gray-700 mb-1">WebUI 地址</label>
|
|
62
|
+
<input id="swal-webui-url" type="text" value="${V()}"
|
|
63
|
+
class="w-full p-2.5 border border-gray-300 rounded-md mb-4 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
64
|
+
placeholder="http://localhost:6600">
|
|
65
|
+
<label class="block text-sm font-medium text-gray-700 mb-1">媒体扩展名(每行一个,用逗号或换行分隔)</label>
|
|
66
|
+
<textarea id="swal-media-ext-list" rows="3"
|
|
67
|
+
class="w-full p-2.5 border border-gray-300 rounded-md mb-4 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
68
|
+
placeholder="例如:m3u8, mp4, mkv, avi, mov, wmv, flv, webm, m4v, ts, m3u, m4a, aac, flac, ape, mp3, wav, ogg, wma">${n.join(", ")}</textarea>
|
|
69
|
+
<p class="text-xs text-gray-500 mb-4">支持的媒体文件扩展名,将用于识别和抓取媒体链接</p>
|
|
70
|
+
<label class="block text-sm font-medium text-gray-700 mb-1">排除网址规则(每行一个,支持正则表达式,以 / 开头和结尾)</label>
|
|
71
|
+
<textarea id="swal-exclude-urls" rows="6"
|
|
72
|
+
class="w-full p-2.5 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
73
|
+
placeholder="例如: localhost:6600 /example.com/ 127.0.0.1">${e}</textarea>
|
|
74
|
+
<p class="text-xs text-gray-500 mt-1">匹配的网址将不展示面板且不抓取媒体链接</p>
|
|
75
|
+
</div>
|
|
76
|
+
`,showCancelButton:!0,confirmButtonText:"保存",cancelButtonText:"取消",confirmButtonColor:"#3b82f6",width:"600px",preConfirm:()=>{const o=document.getElementById("swal-webui-url"),i=document.getElementById("swal-exclude-urls"),a=document.getElementById("swal-media-ext-list"),s=o?o.value.trim():"",d=i?i.value.trim():"",c=a?a.value.trim():"",v=window.Swal;if(!s)return v?.showValidationMessage("WebUI 地址不能为空"),!1;const w=c.split(/[,\n\s]+/).map(E=>E.trim()).filter(E=>E);return w.length===0?(v?.showValidationMessage("媒体扩展名列表不能为空"),!1):{url:s,excludeUrls:d,mediaExtList:w}}}).then(o=>{if(o.isConfirmed&&o.value){const i=o.value;GM_setValue(j,i.url),ft(i.excludeUrls);const a=pt(i.mediaExtList),s=window.Swal;a&&s?s.fire({icon:"success",title:"设置已保存",html:`已保存 ${a.length} 个媒体扩展名类型`,timer:2e3,showConfirmButton:!1}):s&&s.fire({icon:"error",title:"保存失败",text:"媒体扩展名列表格式错误",timer:2e3,showConfirmButton:!1})}})}function Ct(t){const e=window.top&&window.top!==window;try{const o=window.open(t,"_blank");if(o&&!o.closed)return!0}catch(o){console.log("[M3U8 Capture] window.open failed:",o)}if(e&&window.top){try{const o=window.top.open(t,"_blank");if(o&&!o.closed)return!0}catch(o){console.log("[M3U8 Capture] window.top.open failed:",o)}try{return window.top.location.href=t,!0}catch(o){console.log("[M3U8 Capture] window.top.location.href failed:",o)}}const n=()=>{const o=window.Swal;o&&o.fire({icon:"info",title:"链接已复制",html:`由于 iframe 限制,链接已复制到剪贴板<br><br><code style="word-break: break-all; background: #f3f4f6; padding: 8px; border-radius: 4px; display: block; font-size: 12px;">${t}</code><br><br>请手动打开`,timer:4e3,showConfirmButton:!0,confirmButtonText:"确定"})};return nt(t).then(()=>n()).catch(()=>{const o=window.Swal;o&&o.fire({icon:"warning",title:"无法复制链接",html:`由于 iframe 限制,请手动复制并打开:<br><br><code style="word-break: break-all; background: #f3f4f6; padding: 8px; border-radius: 4px; display: block; font-size: 12px;">${t}</code>`,confirmButtonText:"确定"})}),!1}function N(){if(m||!x||!ct())return;F();const t=l?.querySelector("#m3u8-capture-list"),e=l?.querySelector("#m3u8-capture-empty"),n=l?.querySelector("#m3u8-capture-count");if(!t||!e||!n)return;if(n.textContent=x.size.toString(),x.size===0){t.classList.add("hidden"),e.classList.remove("hidden");return}t.classList.remove("hidden"),e.classList.add("hidden"),t.innerHTML="",Array.from(x.values()).sort((i,a)=>a.timestamp-i.timestamp).forEach(i=>{const a=document.createElement("div");a.className="border border-gray-200 rounded-lg p-3 bg-white transition-all duration-200 shadow-sm hover:bg-gray-50 hover:shadow-md";const s=i.title||"",d=i.type.toUpperCase();let c="bg-gray-500";d==="M3U8"||d==="M3U"?c="bg-blue-500":["MP4","MKV","AVI","MOV","WMV","FLV","WEBM","M4V","TS"].includes(d)?c="bg-green-500":["MP3","M4A","AAC","FLAC","APE","WAV","OGG","WMA"].includes(d)&&(c="bg-purple-500"),a.innerHTML=`
|
|
77
|
+
<div class="flex justify-between items-start gap-2 mb-2">
|
|
78
|
+
<div class="flex-1 min-w-0">
|
|
79
|
+
<div class="flex items-center gap-1.5 mb-1.5">
|
|
80
|
+
<span class="font-semibold text-[13px] text-gray-900 overflow-hidden text-ellipsis whitespace-nowrap" title="${s}">${s||"未命名媒体"}</span>
|
|
81
|
+
<span class="${c} text-white px-2 py-0.5 rounded-xl text-[10px] font-bold">${d}</span>
|
|
82
|
+
</div>
|
|
83
|
+
<div class="text-[11px] text-gray-500 overflow-hidden text-ellipsis whitespace-nowrap leading-snug max-w-[320px]" title="${i.url}">${i.url}</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
<div class="flex gap-2">
|
|
87
|
+
<button class="m3u8-capture-download-btn flex-1 bg-blue-500 text-white border-none px-3.5 py-2 rounded-md cursor-pointer text-xs font-medium transition-all duration-200 hover:bg-blue-600 hover:-translate-y-0.5" data-url="${encodeURIComponent(i.url)}" data-title="${encodeURIComponent(s)}">
|
|
88
|
+
跳转下载
|
|
89
|
+
</button>
|
|
90
|
+
<button class="m3u8-capture-copy-btn bg-gray-500 text-white border-none px-3.5 py-2 rounded-md cursor-pointer text-xs transition-all duration-200 hover:bg-gray-600" data-url="${i.url}">
|
|
91
|
+
复制
|
|
92
|
+
</button>
|
|
93
|
+
</div>
|
|
94
|
+
`,t.appendChild(a)}),l?.querySelectorAll(".m3u8-capture-download-btn").forEach(i=>{i.addEventListener("click",a=>{a.stopPropagation();const s=decodeURIComponent(i.getAttribute("data-url")||""),d=decodeURIComponent(i.getAttribute("data-title")||""),c=`${V()}/page/download?from=capture&action=new&url=${encodeURIComponent(s+(d?`|${d}`:""))}`;Ct(c)})}),l?.querySelectorAll(".m3u8-capture-copy-btn").forEach(i=>{i.addEventListener("click",async a=>{a.stopPropagation();const s=i.getAttribute("data-url")||"",d=i.textContent,c=i.className;try{await nt(s),i.textContent="已复制",i.className="m3u8-capture-copy-btn bg-green-500 text-white border-none px-3.5 py-2 rounded-md cursor-pointer text-xs transition-all duration-200",setTimeout(()=>{i.textContent=d,i.className=c},2e3)}catch{const w=window.Swal;if(!w)return;w.fire({icon:"error",title:"复制失败",text:"请手动复制链接",html:`<code style="word-break: break-all; background: #f3f4f6; padding: 8px; border-radius: 4px; display: block; font-size: 12px;">${s}</code>`,confirmButtonText:"确定"})}})})}const I=new Map;Mt(I);function h(t,e=""){if(t=yt(t)||t,!t)return;if(m){try{const i={url:t,title:e||q(),type:it(t),pageUrl:window.location.href,timestamp:Date.now()};window.top?.postMessage({type:"m3u8-capture-link",data:i},"*")}catch(i){console.warn("[M3U8 Capture] Failed to send link to top window:",i)}return}const n=ht(t);I.get(n)?.title||(I.set(n,{url:t,title:e||q(),type:it(t),pageUrl:window.location.href,timestamp:Date.now()}),N())}function Lt(){if(k())return;m||window.addEventListener("message",n=>{if(n.data?.type==="m3u8-capture-link"&&n.data.data){const o=n.data.data;h(o.url,o.title)}}),bt(),vt();const t=()=>{if(!k()){if(m)return A();document.body&&(A(),I.size>0&&N())}};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",t):setTimeout(t,100);let e=location.href;new MutationObserver(()=>{const n=location.href;if(n!==e){if(e=n,k()){H();return}setTimeout(()=>A(),1e3)}}).observe(document,{subtree:!0,childList:!0}),setInterval(()=>{document.body&&!k()&&A()},5e3)}Lt()})();
|
package/client/play.html
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="zh">
|
|
2
|
+
<html lang="zh-CN">
|
|
3
3
|
|
|
4
4
|
<head>
|
|
5
|
-
<title>M3U8
|
|
5
|
+
<title>M3U8 Player</title>
|
|
6
6
|
<meta charset="utf-8">
|
|
7
7
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
8
|
-
<meta content="width=device-width, initial-scale=1.0,
|
|
8
|
+
<meta content="width=device-width, initial-scale=1.0, viewport-fit=cover"
|
|
9
9
|
name="viewport" />
|
|
10
|
+
<link rel="shortcut icon" href="logo.png">
|
|
10
11
|
<style>
|
|
11
12
|
body {
|
|
12
13
|
margin: 0;
|
|
14
|
+
padding: 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#dplayer {
|
|
18
|
+
width: 100vw;
|
|
19
|
+
height: 100vh;
|
|
13
20
|
}
|
|
14
21
|
</style>
|
|
15
22
|
</head>
|
|
@@ -19,25 +26,225 @@
|
|
|
19
26
|
<script src="https://s4.zstatic.net/ajax/libs/hls.js/1.5.18/hls.min.js"
|
|
20
27
|
integrity="sha512-hARxLWym80kd0Bzl5/93OuW1ujaKfvmJ90yTKak/RB67JuNIjtErU2H7H3bteyfzMuqiSK0tXarT7eK6lEWBBA=="
|
|
21
28
|
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
|
22
|
-
<script src="https://s4.zstatic.net/ajax/libs/dplayer/1.26.0/DPlayer.min.js" crossorigin="anonymous"
|
|
23
|
-
referrerpolicy="no-referrer"></script>
|
|
24
29
|
|
|
25
30
|
<script>
|
|
26
|
-
const
|
|
27
|
-
if (!
|
|
31
|
+
const playUrl = location.href.split('url=')[1];
|
|
32
|
+
if (!playUrl) {
|
|
28
33
|
document.getElementById('dplayer').innerText = '请传入播放地址参数 url=';
|
|
29
34
|
} else {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
const CDN_CONFIG = {
|
|
36
|
+
// m3u8Demo: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8',
|
|
37
|
+
dplayer: 'https://s4.zstatic.net/ajax/libs/dplayer/1.26.0/DPlayer.min.js',
|
|
38
|
+
artplayer: [
|
|
39
|
+
'https://s4.zstatic.net/ajax/libs/artplayer/5.3.0/artplayer.min.js',
|
|
40
|
+
// 'https://fastly.jsdelivr.net/npm/artplayer-plugin-hls-control/dist/artplayer-plugin-hls-control.min.js',
|
|
41
|
+
],
|
|
42
|
+
}
|
|
43
|
+
const T = {
|
|
44
|
+
data: {
|
|
45
|
+
playType: playUrl.includes('dplayer') ? 'dplayer' : 'artplayer',
|
|
46
|
+
videoUrl: decodeURIComponent(playUrl),
|
|
47
|
+
dpInstance: null,
|
|
48
|
+
artInstance: null,
|
|
49
|
+
},
|
|
50
|
+
play(videoUrl, playType) {
|
|
51
|
+
if (videoUrl && videoUrl !== T.data.videoUrl) T.data.videoUrl = videoUrl;
|
|
52
|
+
if (playType && playType !== T.data.playType) T.data.playType = playType;
|
|
53
|
+
if (!['dplayer', 'artplayer'].includes(T.data.playType)) T.data.playType = 'artplayer';
|
|
54
|
+
|
|
55
|
+
if (T.data.playType === 'dplayer') {
|
|
56
|
+
return T.dplayer(T.data.videoUrl);
|
|
57
|
+
} else {
|
|
58
|
+
return T.artplayer(T.data.videoUrl);
|
|
59
|
+
}
|
|
36
60
|
},
|
|
37
|
-
|
|
38
|
-
|
|
61
|
+
async loadJS(url) {
|
|
62
|
+
if (Array.isArray(url)) {
|
|
63
|
+
for (const u of url) {
|
|
64
|
+
await T.loadJS(u);
|
|
65
|
+
}
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (document.querySelector(`script[src="${url}"]`)) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
const script = document.createElement('script');
|
|
74
|
+
script.src = url;
|
|
75
|
+
script.onload = resolve;
|
|
76
|
+
script.onerror = reject;
|
|
77
|
+
document.body.appendChild(script);
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
sleep(ms) {
|
|
81
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
82
|
+
},
|
|
83
|
+
async dplayer(videoUrl) {
|
|
84
|
+
if (T.data.dpInstance) {
|
|
85
|
+
T.data.dpInstance.destroy();
|
|
86
|
+
T.data.dpInstance = null;
|
|
87
|
+
await T.sleep(100);
|
|
88
|
+
} else {
|
|
89
|
+
await T.loadJS(CDN_CONFIG.dplayer);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const dp = new DPlayer({
|
|
93
|
+
container: document.getElementById('dplayer'),
|
|
94
|
+
autoplay: true,
|
|
95
|
+
airplay: true,
|
|
96
|
+
theme: '#FADFA3',
|
|
97
|
+
loop: true,
|
|
98
|
+
screenshot: true,
|
|
99
|
+
hotkey: true,
|
|
100
|
+
chromecast: true,
|
|
101
|
+
preload: 'auto',
|
|
102
|
+
playbackSpeed: [0.5, 0.75, 1, 1.25, 1.5, 2, 3, 4, 8],
|
|
103
|
+
video: {
|
|
104
|
+
url: videoUrl,
|
|
105
|
+
type: 'auto',
|
|
106
|
+
},
|
|
107
|
+
pluginOptions: {
|
|
108
|
+
hls: {},
|
|
109
|
+
},
|
|
110
|
+
contextmenu: [
|
|
111
|
+
{ text: '在线播放器', link: 'https://m3u8-player.lzw.me' },
|
|
112
|
+
{ text: '在线下载器', link: 'https://m3u8-downloader.lzw.me' },
|
|
113
|
+
],
|
|
114
|
+
});
|
|
115
|
+
dp.on('ended', () => {
|
|
116
|
+
console.log('[dplayer]播放完毕', videoUrl);
|
|
117
|
+
T.next_video();
|
|
118
|
+
});
|
|
119
|
+
T.data.dpInstance = dp;
|
|
120
|
+
return dp;
|
|
39
121
|
},
|
|
40
|
-
|
|
122
|
+
/** 使用 artplayer 播放 */
|
|
123
|
+
async artplayer(videoUrl) {
|
|
124
|
+
if (T.data.artInstance) {
|
|
125
|
+
T.data.artInstance.destroy();
|
|
126
|
+
T.data.artInstance = null;
|
|
127
|
+
await T.sleep(100);
|
|
128
|
+
} else {
|
|
129
|
+
await T.loadJS(CDN_CONFIG.artplayer);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const art = new Artplayer({
|
|
133
|
+
container: document.getElementById('dplayer'),
|
|
134
|
+
url: videoUrl, // 'https://playertest.longtailvideo.com/adaptive/elephants_dream_v4/index.m3u8',
|
|
135
|
+
// airplay: true,
|
|
136
|
+
aspectRatio: true, // 是否显示视频长宽比功能
|
|
137
|
+
autoplay: true,
|
|
138
|
+
autoOrientation: true,
|
|
139
|
+
// autoMini: true, // 当播放器滚动到浏览器视口以外时,自动进入 迷你播放 模式
|
|
140
|
+
autoPlayback: true,
|
|
141
|
+
// autoSize: true, // 自动调整播放器尺寸
|
|
142
|
+
fastForward: true,
|
|
143
|
+
flip: true, // 是否显示视频翻转功能
|
|
144
|
+
fullscreen: true, // 是否在底部控制栏里显示播放器 窗口全屏 按钮
|
|
145
|
+
fullscreenWeb: true, // 是否在底部控制栏里显示播放器 网页全屏 按钮
|
|
146
|
+
lock: true,
|
|
147
|
+
miniProgressBar: true,
|
|
148
|
+
pip: true, // 是否在底部控制栏里显示 画中画 的开关按钮
|
|
149
|
+
playbackRate: true, // 是否显示视频播放速度功能
|
|
150
|
+
screenshot: true,
|
|
151
|
+
setting: true,
|
|
152
|
+
theme: '#39f',
|
|
153
|
+
type: videoUrl.includes('mp4') ? 'mp4' : 'm3u8',
|
|
154
|
+
customType: {
|
|
155
|
+
m3u8: function playM3u8(video, url, art) {
|
|
156
|
+
if (Hls.isSupported()) {
|
|
157
|
+
if (art.hls) art.hls.destroy();
|
|
158
|
+
const hls = new Hls();
|
|
159
|
+
hls.loadSource(url);
|
|
160
|
+
hls.attachMedia(video);
|
|
161
|
+
art.hls = hls;
|
|
162
|
+
art.on('destroy', () => hls.destroy());
|
|
163
|
+
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
|
164
|
+
video.src = url;
|
|
165
|
+
} else {
|
|
166
|
+
art.notice.show = 'Unsupported playback format: m3u8';
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
flv: function playFlv(video, url, art) {
|
|
170
|
+
if (flvjs.isSupported()) {
|
|
171
|
+
if (art.flv) art.flv.destroy();
|
|
172
|
+
const flv = flvjs.createPlayer({ type: 'flv', url });
|
|
173
|
+
flv.attachMediaElement(video);
|
|
174
|
+
flv.load();
|
|
175
|
+
art.flv = flv;
|
|
176
|
+
art.on('destroy', () => flv.destroy());
|
|
177
|
+
} else {
|
|
178
|
+
art.notice.show = 'Unsupported playback format: flv';
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
controls: [{
|
|
183
|
+
name: "previous-button",
|
|
184
|
+
index: 10,
|
|
185
|
+
position: "left",
|
|
186
|
+
html: '<svg fill="none" stroke-width="2" xmlns="http://www.w3.org/2000/svg" height="22" width="22" class="icon icon-tabler icon-tabler-player-track-prev-filled" width="1em" height="1em" viewBox="0 0 24 24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" style="overflow: visible; color: currentcolor;"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M20.341 4.247l-8 7a1 1 0 0 0 0 1.506l8 7c.647 .565 1.659 .106 1.659 -.753v-14c0 -.86 -1.012 -1.318 -1.659 -.753z" stroke-width="0" fill="currentColor"></path><path d="M9.341 4.247l-8 7a1 1 0 0 0 0 1.506l8 7c.647 .565 1.659 .106 1.659 -.753v-14c0 -.86 -1.012 -1.318 -1.659 -.753z" stroke-width="0" fill="currentColor"></path></svg>',
|
|
187
|
+
tooltip: "Previous",
|
|
188
|
+
click: function () {
|
|
189
|
+
T.previous_video()
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: "next-button",
|
|
194
|
+
index: 11,
|
|
195
|
+
position: "left",
|
|
196
|
+
html: '<svg fill="none" stroke-width="2" xmlns="http://www.w3.org/2000/svg" height="22" width="22" class="icon icon-tabler icon-tabler-player-track-next-filled" width="1em" height="1em" viewBox="0 0 24 24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" style="overflow: visible; color: currentcolor;"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M2 5v14c0 .86 1.012 1.318 1.659 .753l8 -7a1 1 0 0 0 0 -1.506l-8 -7c-.647 -.565 -1.659 -.106 -1.659 .753z" stroke-width="0" fill="currentColor"></path><path d="M13 5v14c0 .86 1.012 1.318 1.659 .753l8 -7a1 1 0 0 0 0 -1.506l-8 -7c-.647 -.565 -1.659 -.106 -1.659 .753z" stroke-width="0" fill="currentColor"></path></svg>',
|
|
197
|
+
tooltip: "Next",
|
|
198
|
+
click: function () {
|
|
199
|
+
T.next_video()
|
|
200
|
+
},
|
|
201
|
+
}],
|
|
202
|
+
contextmenu: [
|
|
203
|
+
{ html: '在线播放器', click: () => window.open('https://m3u8-player.lzw.me', '_blank') },
|
|
204
|
+
{ html: '在线下载器', click: () => window.open('https://m3u8-downloader.lzw.me', '_blank') },
|
|
205
|
+
],
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
art.on('video:ended', () => {
|
|
209
|
+
console.log('[artplayer]播放完毕', videoUrl);
|
|
210
|
+
T.next_video();
|
|
211
|
+
});
|
|
212
|
+
// console.log('[artplayer]播放器初始化完成', art);
|
|
213
|
+
T.data.artInstance = art;
|
|
214
|
+
return art;
|
|
215
|
+
},
|
|
216
|
+
previous_video() {
|
|
217
|
+
T.postMessage({ action: 'previous', url: T.data.videoUrl, playType: T.data.playType });
|
|
218
|
+
},
|
|
219
|
+
next_video() {
|
|
220
|
+
T.postMessage({ action: 'next', url: T.data.videoUrl, playType: T.data.playType });
|
|
221
|
+
},
|
|
222
|
+
postMessage(message) {
|
|
223
|
+
if (window.sef === window.parent) {
|
|
224
|
+
return;
|
|
225
|
+
} else {
|
|
226
|
+
window.parent.postMessage(message, '*');
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
init() {
|
|
230
|
+
if (window.sef !== window.parent) {
|
|
231
|
+
// 监听来自父窗口的消息
|
|
232
|
+
window.addEventListener('message', (event) => {
|
|
233
|
+
const data = event.data;
|
|
234
|
+
if (!data) return;
|
|
235
|
+
|
|
236
|
+
if (typeof data === 'object' && data.url) {
|
|
237
|
+
T.data.videoUrl = data.url;
|
|
238
|
+
if (data.playType) T.data.playType = data.playType;
|
|
239
|
+
T.play();
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
return T.play();
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
T.init();
|
|
247
|
+
window.T = T;
|
|
41
248
|
}
|
|
42
249
|
</script>
|
|
43
250
|
</body>
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lzwme/m3u8-dl",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.6.0",
|
|
4
|
+
"description": "A free, open-source, and powerful m3u8 video batch downloader with multi-threaded downloading, play-while-downloading, WebUI management, video parsing, and more.",
|
|
5
5
|
"main": "cjs/index.js",
|
|
6
6
|
"types": "cjs/index.d.ts",
|
|
7
7
|
"license": "MIT",
|
|
8
|
-
"repository":
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/lzwme/m3u8-dl.git"
|
|
11
|
+
},
|
|
9
12
|
"author": {
|
|
10
13
|
"name": "renxia",
|
|
11
14
|
"email": "lzwy0820@qq.com",
|
|
@@ -13,17 +16,26 @@
|
|
|
13
16
|
},
|
|
14
17
|
"scripts": {
|
|
15
18
|
"prepare": "husky || true",
|
|
16
|
-
"dev": "
|
|
17
|
-
"
|
|
19
|
+
"dev": "concurrently \"pnpm dev:sdk\" \"pnpm dev:server\" \"pnpm dev:frontend\"",
|
|
20
|
+
"dev:sdk": "tsc -p tsconfig.cjs.json --watch",
|
|
21
|
+
"dev:frontend": "pnpm -F @lzwme/m3u8-dl-frontend dev",
|
|
22
|
+
"dev:app": "pnpm -F @lzwme/m3u8-dl-app dev",
|
|
23
|
+
"dev:server": "nodemon bin/m3u8dl.js server --debug",
|
|
18
24
|
"lint:flh": "flh --eslint --tscheck --prettier",
|
|
19
|
-
"lint": "biome lint
|
|
20
|
-
"
|
|
25
|
+
"lint": "biome lint",
|
|
26
|
+
"lint:all": "biome lint && pnpm -F @lzwme/m3u8-capture lint && pnpm -F @lzwme/m3u8-dl-frontend lint",
|
|
27
|
+
"format": "biome format --write",
|
|
28
|
+
"fix": "biome check --fix",
|
|
29
|
+
"build": "npm run clean && npm run build:cjs && npm run build:frontend && npm run build:capture",
|
|
21
30
|
"build:cjs": "tsc -p tsconfig.cjs.json",
|
|
31
|
+
"build:frontend": "pnpm -F @lzwme/m3u8-dl-frontend build",
|
|
32
|
+
"build:capture": "pnpm -F @lzwme/m3u8-capture build",
|
|
33
|
+
"download-cdn": "node scripts/download-cdn-resources.js",
|
|
22
34
|
"doc": "typedoc src/ --exclude **/*.spec.ts --out docs --tsconfig tsconfig.module.json",
|
|
23
35
|
"version": "standard-version",
|
|
24
36
|
"dist": "npm run build",
|
|
25
37
|
"release": "npm run dist && npm run version",
|
|
26
|
-
"clean": "flh rm -f ./cjs ./esm ./docs",
|
|
38
|
+
"clean": "flh rm -f ./cjs ./esm ./docs ./client/assets",
|
|
27
39
|
"test": "npm run lint"
|
|
28
40
|
},
|
|
29
41
|
"bin": {
|
|
@@ -44,32 +56,33 @@
|
|
|
44
56
|
"registry": "https://registry.npmjs.com"
|
|
45
57
|
},
|
|
46
58
|
"devDependencies": {
|
|
47
|
-
"@biomejs/biome": "^2.
|
|
48
|
-
"@eslint/js": "^9.
|
|
59
|
+
"@biomejs/biome": "^2.3.5",
|
|
60
|
+
"@eslint/js": "^9.39.1",
|
|
49
61
|
"@lzwme/fed-lint-helper": "^2.6.6",
|
|
50
|
-
"@types/express": "^5.0.
|
|
51
|
-
"@types/m3u8-parser": "^7.2.
|
|
52
|
-
"@types/node": "^24.
|
|
62
|
+
"@types/express": "^5.0.5",
|
|
63
|
+
"@types/m3u8-parser": "^7.2.5",
|
|
64
|
+
"@types/node": "^24.10.0",
|
|
53
65
|
"@types/ws": "^8.18.1",
|
|
54
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
55
|
-
"@typescript-eslint/parser": "^8.
|
|
56
|
-
"
|
|
66
|
+
"@typescript-eslint/eslint-plugin": "^8.46.4",
|
|
67
|
+
"@typescript-eslint/parser": "^8.46.4",
|
|
68
|
+
"concurrently": "^9.2.1",
|
|
69
|
+
"eslint": "^9.39.1",
|
|
57
70
|
"eslint-config-prettier": "^10.1.8",
|
|
58
71
|
"eslint-plugin-prettier": "^5.5.4",
|
|
59
72
|
"express": "^5.1.0",
|
|
60
73
|
"husky": "^9.1.7",
|
|
74
|
+
"nodemon": "^3.1.11",
|
|
61
75
|
"prettier": "^3.6.2",
|
|
62
76
|
"standard-version": "^9.5.0",
|
|
63
|
-
"typescript": "^5.9.
|
|
64
|
-
"typescript-eslint": "^8.
|
|
77
|
+
"typescript": "^5.9.3",
|
|
78
|
+
"typescript-eslint": "^8.46.4",
|
|
65
79
|
"ws": "^8.18.3"
|
|
66
80
|
},
|
|
67
81
|
"dependencies": {
|
|
68
|
-
"@lzwme/fe-utils": "^1.9.
|
|
69
|
-
"commander": "^14.0.
|
|
82
|
+
"@lzwme/fe-utils": "^1.9.2",
|
|
83
|
+
"commander": "^14.0.2",
|
|
70
84
|
"console-log-colors": "^0.5.0",
|
|
71
85
|
"enquirer": "^2.4.1",
|
|
72
|
-
"ffmpeg-static": "^5.2.0",
|
|
73
86
|
"m3u8-parser": "^7.2.0"
|
|
74
87
|
},
|
|
75
88
|
"files": [
|
package/client/style.css
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
::placeholder {
|
|
2
|
-
font-size: 14px;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
.swal2-html-container textarea, input {
|
|
6
|
-
font-size: 16px !important;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
#app {
|
|
10
|
-
max-width: 1400px;
|
|
11
|
-
margin: auto;
|
|
12
|
-
min-height: 100vh;
|
|
13
|
-
background-color: #f5f5f5;
|
|
14
|
-
position: relative;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.sidebar {
|
|
18
|
-
background-color: #fff;
|
|
19
|
-
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
|
|
20
|
-
transition: all 0.3s ease;
|
|
21
|
-
position: absolute;
|
|
22
|
-
left: 0;
|
|
23
|
-
top: 0;
|
|
24
|
-
bottom: 0;
|
|
25
|
-
width: 16rem;
|
|
26
|
-
z-index: 1;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.download-item {
|
|
30
|
-
transition: all 0.3s ease;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.download-item:hover {
|
|
34
|
-
background-color: #f8f9fa;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
.progress-bar {
|
|
38
|
-
height: 4px;
|
|
39
|
-
transition: width 0.3s ease;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
.nav-item {
|
|
43
|
-
transition: all 0.3s ease;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.nav-item:hover {
|
|
47
|
-
background-color: #f0f0f0;
|
|
48
|
-
transform: translateX(4px);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.nav-item.active {
|
|
52
|
-
background-color: #e6f3ff;
|
|
53
|
-
color: #1890ff;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/* 移动端适配 */
|
|
57
|
-
@media (max-width: 768px) {
|
|
58
|
-
.sidebar {
|
|
59
|
-
transform: translateX(-100%);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.sidebar.show {
|
|
63
|
-
transform: translateX(0);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
.menu-toggle {
|
|
67
|
-
display: block !important;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
.main-content {
|
|
71
|
-
margin-left: 0 !important;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.menu-toggle {
|
|
76
|
-
display: none;
|
|
77
|
-
position: fixed;
|
|
78
|
-
top: 1rem;
|
|
79
|
-
left: 1rem;
|
|
80
|
-
z-index: 51;
|
|
81
|
-
padding: 0.5rem;
|
|
82
|
-
background: white;
|
|
83
|
-
border-radius: 0.5rem;
|
|
84
|
-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
85
|
-
box-shadow: 0 2px 4px #9bdff5;
|
|
86
|
-
width: 42px;
|
|
87
|
-
height: 42px;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
.main-content {
|
|
91
|
-
transition: margin-left 0.3s ease;
|
|
92
|
-
margin-left: 16rem;
|
|
93
|
-
width: calc(100% - 16rem);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/* 自定义toast样式 */
|
|
97
|
-
.custom-toast {
|
|
98
|
-
position: fixed;
|
|
99
|
-
top: 20px;
|
|
100
|
-
right: 20px;
|
|
101
|
-
min-width: 250px;
|
|
102
|
-
max-width: 400px;
|
|
103
|
-
padding: 15px 25px 15px 15px;
|
|
104
|
-
border-radius: 4px;
|
|
105
|
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
106
|
-
background: #fff;
|
|
107
|
-
color: #333;
|
|
108
|
-
z-index: 99999;
|
|
109
|
-
display: flex;
|
|
110
|
-
align-items: center;
|
|
111
|
-
transform: translateX(150%);
|
|
112
|
-
transition: transform 0.3s ease;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
.custom-toast.show {
|
|
116
|
-
transform: translateX(0);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.custom-toast.hide {
|
|
120
|
-
transform: translateX(150%);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
.custom-toast-success {
|
|
124
|
-
border-left: 4px solid #28a745;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
.custom-toast-error {
|
|
128
|
-
border-left: 4px solid #dc3545;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
.custom-toast-warning {
|
|
132
|
-
border-left: 4px solid #ffc107;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
.custom-toast-info {
|
|
136
|
-
border-left: 4px solid #17a2b8;
|
|
137
|
-
}
|