Created
May 9, 2026 00:32
-
-
Save luckycdev/284a5c27865fc6036c2ab197d5346380 to your computer and use it in GitHub Desktop.
HLS and Subtitle Link Finder Userscript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name HLS and Subtitle Link Finder | |
| // @namespace https://github.com/luckycdev | |
| // @version 1.0 | |
| // @description Finds HLS (m3u8) and Subtitle (vtt, srt, ass) links and displays them | |
| // @author luckycdev | |
| // @match *://*/* | |
| // @grant none | |
| // @run-at document-start | |
| // @license MIT | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| const assets = { video: new Set(), subtitles: new Set() }; | |
| const isMainPage = (window.self === window.top); | |
| let isTerminated = false; | |
| let isMinimized = false; | |
| function intercept(url) { | |
| if (isTerminated || typeof url !== 'string') return; | |
| let type = null; | |
| if (url.includes('.m3u8')) type = 'video'; | |
| else if (url.includes('.vtt') || url.includes('.ass') || url.includes('.srt')) type = 'subtitles'; | |
| if (type && !assets[type].has(url)) { | |
| assets[type].add(url); | |
| if (!isMainPage) { | |
| window.top.postMessage({ type: 'NEW_ASSET', assetType: type, url: url }, '*'); | |
| } else { | |
| render(); | |
| } | |
| } | |
| } | |
| const open = XMLHttpRequest.prototype.open; | |
| XMLHttpRequest.prototype.open = function(m, url) { intercept(url); return open.apply(this, arguments); }; | |
| const originalFetch = window.fetch; | |
| window.fetch = async (...args) => { | |
| const url = typeof args[0] === 'string' ? args[0] : args[0].url; | |
| intercept(url); | |
| return originalFetch(...args); | |
| }; | |
| function render() { | |
| if (!isMainPage || isTerminated) return; | |
| const target = document.body || document.documentElement; | |
| if (!target) return; | |
| let host = document.getElementById('lf-host'); | |
| if (!host) { | |
| host = document.createElement('div'); | |
| host.id = 'lf-host'; | |
| host.style.cssText = 'position:fixed !important; top:20px !important; right:20px !important; z-index:2147483647 !important; display:block !important;'; | |
| target.appendChild(host); | |
| host.attachShadow({mode: 'open'}); | |
| } | |
| const root = host.shadowRoot; | |
| root.innerHTML = ''; | |
| const style = document.createElement('style'); | |
| style.textContent = ` | |
| .btn { | |
| color: #999; | |
| transition: color 0.3s ease; | |
| cursor: pointer; | |
| padding: 0 4px; | |
| display: inline-block; | |
| user-select: none; | |
| } | |
| .btn:hover { | |
| color: #000 !important; | |
| } | |
| .close-btn:hover { | |
| color: #ff4d4d !important; | |
| } | |
| .title-text { | |
| font-size: 11px; | |
| font-weight: 800; | |
| color: #000; | |
| text-transform: uppercase; | |
| white-space: nowrap !important; /* Force single line */ | |
| overflow: hidden; | |
| } | |
| `; | |
| root.appendChild(style); | |
| const panel = document.createElement('div'); | |
| panel.style.cssText = ` | |
| width: ${isMinimized ? '160px' : '280px'}; /* Increased width */ | |
| background: #ffffff; | |
| color: #000000; | |
| border-radius: 8px; | |
| box-shadow: 0 8px 24px rgba(0,0,0,0.3); | |
| font-family: Arial, sans-serif; | |
| border: 1px solid #ccc; | |
| overflow: hidden; | |
| display: flex; | |
| flex-direction: column; | |
| `; | |
| const header = document.createElement('div'); | |
| header.style.cssText = 'padding:12px; display:flex; justify-content:space-between; align-items:center; cursor:pointer; background:#fff; border-bottom:' + (isMinimized ? 'none' : '2px solid #000'); | |
| header.onclick = (e) => { e.stopPropagation(); isMinimized = !isMinimized; render(); }; | |
| const title = document.createElement('span'); | |
| title.className = 'title-text'; | |
| title.textContent = `LINKS (${assets.video.size + assets.subtitles.size})`; | |
| const controls = document.createElement('div'); | |
| controls.style.cssText = 'display:flex; gap:12px; align-items:center; flex-shrink: 0;'; | |
| const minBtn = document.createElement('span'); | |
| minBtn.className = 'btn'; | |
| minBtn.style.cssText = 'font-size:18px; font-weight:bold;'; | |
| minBtn.textContent = isMinimized ? '+' : '−'; | |
| const closeBtn = document.createElement('span'); | |
| closeBtn.className = 'btn close-btn'; | |
| closeBtn.style.cssText = 'font-size:20px; font-weight:bold;'; | |
| closeBtn.textContent = '×'; | |
| closeBtn.onclick = (e) => { | |
| e.stopPropagation(); | |
| isTerminated = true; | |
| host.remove(); | |
| observer.disconnect(); | |
| }; | |
| controls.append(minBtn, closeBtn); | |
| header.append(title, controls); | |
| panel.appendChild(header); | |
| if (!isMinimized) { | |
| const body = document.createElement('div'); | |
| body.style.cssText = 'overflow-y:auto; max-height:50vh; background:#fff;'; | |
| const sections = [ | |
| {name: 'VIDEO', list: assets.video}, | |
| {name: 'SUBTITLES', list: assets.subtitles} | |
| ]; | |
| sections.forEach(sec => { | |
| if (sec.list.size > 0) { | |
| const sHeader = document.createElement('div'); | |
| sHeader.style.cssText = 'background:#f4f4f4; padding:6px 12px; font-size:10px; font-weight:700; color:#555; border-bottom:1px solid #ddd;'; | |
| sHeader.textContent = sec.name; | |
| body.appendChild(sHeader); | |
| sec.list.forEach(link => { | |
| const row = document.createElement('div'); | |
| row.style.cssText = 'padding:10px 12px; border-bottom:1px solid #eee;'; | |
| const a = document.createElement('a'); | |
| a.href = link; | |
| a.target = '_blank'; | |
| a.style.cssText = 'color:#1a73e8; text-decoration:none; font-size:12px; font-weight:500; word-break:break-all; display:block;'; | |
| a.textContent = link.split('/').pop().split('?')[0] || "File"; | |
| row.appendChild(a); | |
| body.appendChild(row); | |
| }); | |
| } | |
| }); | |
| panel.appendChild(body); | |
| } | |
| root.appendChild(panel); | |
| } | |
| const observer = new MutationObserver(() => { | |
| if (!isTerminated && isMainPage && !document.getElementById('lf-host')) { | |
| render(); | |
| } | |
| }); | |
| window.addEventListener('message', (e) => { | |
| if (isMainPage && !isTerminated && e.data?.type === 'NEW_ASSET') { | |
| assets[e.data.assetType].add(e.data.url); | |
| render(); | |
| } | |
| }); | |
| if (isMainPage) { | |
| const boot = () => { | |
| if (document.body) { | |
| render(); | |
| observer.observe(document.documentElement, { childList: true, subtree: true }); | |
| } else { | |
| requestAnimationFrame(boot); | |
| } | |
| }; | |
| boot(); | |
| setInterval(() => { if (!isTerminated) render(); }, 3000); | |
| } | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment