// ==UserScript== // @name MP4 转 M3U8 外链工具 // @namespace https://github.com/userscripts/mp4-to-m3u8 // @version 1.0.0 // @description 在任意网页上检测 MP4 视频链接,一键转换为 M3U8 外链,支持自定义服务器地址 // @author Auto // @match *://*/* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant GM_setClipboard // @grant GM_notification // @connect * // @run-at document-idle // ==/UserScript== (function () { 'use strict'; /* ======================================================== * 配置项(可在面板中修改并持久保存) * ======================================================== */ const CONFIG = { // 你的后端转换服务地址(见下方 Node.js 服务说明) serverUrl: GM_getValue('serverUrl', 'http://localhost:3700'), // 切片时长(秒) segmentTime: GM_getValue('segmentTime', 10), // 外链基础 URL(留空则使用服务器本地路径) baseUrl: GM_getValue('baseUrl', ''), }; /* ======================================================== * 样式注入 * ======================================================== */ GM_addStyle(` #m3u8-fab { position: fixed; right: 20px; bottom: 80px; width: 48px; height: 48px; border-radius: 50%; background: linear-gradient(135deg, #6366f1, #8b5cf6); color: #fff; font-size: 20px; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 4px 16px rgba(99,102,241,.5); z-index: 2147483647; user-select: none; transition: transform .2s, box-shadow .2s; } #m3u8-fab:hover { transform: scale(1.1); box-shadow: 0 6px 20px rgba(99,102,241,.7); } #m3u8-panel { position: fixed; right: 20px; bottom: 140px; width: 400px; max-height: 80vh; background: #1e1e2e; color: #cdd6f4; border-radius: 14px; box-shadow: 0 12px 40px rgba(0,0,0,.6); z-index: 2147483646; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 13px; display: none; flex-direction: column; overflow: hidden; animation: m3u8-slide-in .2s ease; } @keyframes m3u8-slide-in { from { opacity:0; transform: translateY(12px); } to { opacity:1; transform: translateY(0); } } #m3u8-panel.open { display: flex; } .m3u8-header { padding: 14px 16px 10px; background: #181825; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid rgba(255,255,255,.06); } .m3u8-header h3 { margin: 0; font-size: 14px; font-weight: 600; color: #cba6f7; display: flex; align-items: center; gap: 6px; } .m3u8-close { cursor: pointer; color: #6c7086; font-size: 18px; line-height: 1; padding: 2px 4px; border-radius: 4px; } .m3u8-close:hover { color: #cdd6f4; background: rgba(255,255,255,.06); } .m3u8-tabs { display: flex; background: #181825; border-bottom: 1px solid rgba(255,255,255,.06); } .m3u8-tab { flex: 1; padding: 8px 0; text-align: center; cursor: pointer; color: #6c7086; font-size: 12px; transition: color .15s; } .m3u8-tab.active { color: #cba6f7; border-bottom: 2px solid #cba6f7; } .m3u8-body { flex: 1; overflow-y: auto; padding: 12px 14px; } .m3u8-body::-webkit-scrollbar { width: 4px; } .m3u8-body::-webkit-scrollbar-thumb { background: #45475a; border-radius: 4px; } .m3u8-section { margin-bottom: 14px; } .m3u8-label { font-size: 11px; color: #6c7086; margin-bottom: 4px; text-transform: uppercase; letter-spacing: .5px; } .m3u8-input { width: 100%; padding: 7px 10px; background: #313244; border: 1px solid rgba(255,255,255,.08); border-radius: 7px; color: #cdd6f4; font-size: 12px; outline: none; box-sizing: border-box; transition: border-color .15s; } .m3u8-input:focus { border-color: #cba6f7; } .m3u8-row { display: flex; gap: 8px; } .m3u8-row .m3u8-input { flex: 1; } .m3u8-btn { padding: 7px 14px; border: none; border-radius: 7px; cursor: pointer; font-size: 12px; font-weight: 600; transition: all .15s; white-space: nowrap; } .m3u8-btn-primary { background: linear-gradient(135deg, #6366f1, #8b5cf6); color: #fff; } .m3u8-btn-primary:hover { filter: brightness(1.1); } .m3u8-btn-primary:disabled { opacity: .5; cursor: not-allowed; filter: none; } .m3u8-btn-ghost { background: rgba(255,255,255,.06); color: #cdd6f4; } .m3u8-btn-ghost:hover { background: rgba(255,255,255,.1); } .m3u8-result { background: #181825; border-radius: 8px; padding: 10px; margin-top: 10px; display: none; } .m3u8-result.show { display: block; } .m3u8-result-url { font-family: "SF Mono","Fira Code",monospace; font-size: 11px; color: #a6e3a1; word-break: break-all; margin-bottom: 8px; } .m3u8-result-btns { display: flex; gap: 6px; flex-wrap: wrap; } .m3u8-link-item { background: #313244; border-radius: 8px; padding: 8px 10px; margin-bottom: 6px; cursor: pointer; transition: background .15s; display: flex; align-items: center; gap: 8px; } .m3u8-link-item:hover { background: #45475a; } .m3u8-link-item.selected { border: 1px solid #cba6f7; } .m3u8-link-text { flex: 1; font-size: 11px; color: #89b4fa; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .m3u8-link-badge { font-size: 10px; padding: 2px 6px; border-radius: 4px; background: rgba(137,180,250,.15); color: #89b4fa; } .m3u8-status { display: flex; align-items: center; gap: 6px; font-size: 12px; padding: 6px 0; } .m3u8-status-dot { width: 8px; height: 8px; border-radius: 50%; background: #6c7086; flex-shrink: 0; } .m3u8-status-dot.running { background: #f9e2af; animation: m3u8-pulse .8s infinite; } .m3u8-status-dot.ok { background: #a6e3a1; } .m3u8-status-dot.err { background: #f38ba8; } @keyframes m3u8-pulse { 0%,100% { opacity:1; } 50% { opacity:.3; } } .m3u8-progress { height: 4px; background: #313244; border-radius: 4px; margin-top: 8px; overflow: hidden; } .m3u8-progress-bar { height: 100%; background: linear-gradient(90deg, #6366f1, #a6e3a1); border-radius: 4px; width: 0%; transition: width .3s; } .m3u8-settings-row { display: flex; align-items: center; justify-content: space-between; padding: 6px 0; border-bottom: 1px solid rgba(255,255,255,.04); } .m3u8-settings-row:last-child { border-bottom: none; } .m3u8-settings-key { color: #a6adc8; font-size: 12px; } .m3u8-settings-val { color: #cdd6f4; font-size: 12px; } .m3u8-save-tip { font-size: 11px; color: #a6e3a1; margin-top: 6px; display: none; } .m3u8-empty { text-align: center; padding: 24px 0; color: #45475a; font-size: 12px; } `); /* ======================================================== * DOM 构建 * ======================================================== */ const fab = document.createElement('div'); fab.id = 'm3u8-fab'; fab.title = 'MP4 → M3U8 工具'; fab.innerHTML = '📹'; document.body.appendChild(fab); const panel = document.createElement('div'); panel.id = 'm3u8-panel'; panel.innerHTML = `

📹 MP4 → M3U8 外链工具

🔄 转换
🔍 检测
⚙️ 设置
MP4 视频 URL
切片时长 (秒)
外链基础 URL (可选)
等待输入...
M3U8 外链地址
`; document.body.appendChild(panel); /* ======================================================== * 状态 * ======================================================== */ let currentTab = 'convert'; let detectedLinks = []; let selectedLink = null; let resultM3u8Url = ''; let resultPlayerUrl = ''; /* ======================================================== * 工具函数 * ======================================================== */ function setStatus(msg, state = '') { const dot = document.getElementById('m3u8-dot'); const txt = document.getElementById('m3u8-status-text'); if (dot) { dot.className = 'm3u8-status-dot' + (state ? ` ${state}` : ''); } if (txt) txt.textContent = msg; } function setProgress(pct) { const bar = document.getElementById('m3u8-progress'); const fill = document.getElementById('m3u8-progress-bar'); if (!bar || !fill) return; if (pct < 0) { bar.style.display = 'none'; } else { bar.style.display = 'block'; fill.style.width = pct + '%'; } } function showResult(m3u8Url, playerUrl) { resultM3u8Url = m3u8Url; resultPlayerUrl = playerUrl; const box = document.getElementById('m3u8-result'); const urlEl = document.getElementById('m3u8-result-url'); if (box) box.classList.add('show'); if (urlEl) urlEl.textContent = m3u8Url; } function copyText(text) { try { GM_setClipboard(text); GM_notification({ title: 'M3U8 外链已复制', text: text.slice(0, 80), timeout: 2500 }); } catch (e) { navigator.clipboard.writeText(text).catch(() => {}); } } /* ======================================================== * 页面视频链接检测 * ======================================================== */ function detectVideoLinks() { const found = new Map(); // 1.