Greasy Fork

来自缓存

Greasy Fork is available in English.

资源嗅探

资源嗅探工具,支持 M3U8 解析下载,优化 UI,增强交互体验

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         资源嗅探
// @namespace    https://staybrowser.com/
// @version      1.1.2
// @description  资源嗅探工具,支持 M3U8 解析下载,优化 UI,增强交互体验
// @author       GaryIndex
// @license      telegram @GaryIndex
// @match        *://*/*
// @grant        none
// @icon         https://raw.githubusercontent.com/GaryIndex/GaryIndex/refs/heads/main/GaryIndex.JPG
// ==/UserScript==
(() => {
    'use strict';
    const snifferBtn = document.createElement('button');
    snifferBtn.innerText = '资源嗅探';
    Object.assign(snifferBtn.style, { position: 'fixed', bottom: '20px', right: '20px', zIndex: '10000', padding: '12px 20px', fontSize: '16px', border: 'none', borderRadius: '25px', backgroundColor: '#007aff', color: '#fff', cursor: 'pointer', boxShadow: '0 4px 8px rgba(0,0,0,0.2)', fontWeight: 'bold' });
    const modal = document.createElement('div');
    Object.assign(modal.style, { position: 'fixed', width: '90%', height: '50vh', left: '5%', top: '25vh', backgroundColor: '#fff', borderRadius: '12px', boxShadow: '0 4px 10px rgba(0,0,0,0.3)', display: 'none', flexDirection: 'column', zIndex: '10001', overflow: 'hidden', fontFamily: 'Arial, sans-serif', fontSize: '14px' });
    const modalHeader = document.createElement('div');
    Object.assign(modalHeader.style, { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '12px 16px', fontSize: '16px', fontWeight: 'bold', color: '#000' });
    const headerImage = document.createElement('img');
    headerImage.src = 'https://raw.githubusercontent.com/GaryIndex/GaryIndex/refs/heads/main/GaryIndex.JPG';
    headerImage.alt = '资源图标';
    Object.assign(headerImage.style, { width: '30px', height: '30px', marginRight: '10px', objectFit: 'cover', borderRadius: '5px' });
    const headerText = document.createElement('span');
    headerText.innerText = '选择要下载的资源';
    Object.assign(headerText.style, { display: 'inline-block', lineHeight: '30px' });
    const closeBtn = document.createElement('span');
    closeBtn.innerText = '关闭';
    Object.assign(closeBtn.style, { cursor: 'pointer', fontWeight: 'bold', color: '#007aff' });
    closeBtn.onclick = () => { modal.style.display = 'none'; snifferBtn.style.display = 'block'; };
    const leftContent = document.createElement('div');
    leftContent.style.display = 'flex'; leftContent.style.alignItems = 'center';
    leftContent.appendChild(headerImage);
    leftContent.appendChild(headerText);
    modalHeader.appendChild(leftContent);
    modalHeader.appendChild(closeBtn);
    const categories = ['全部', '视频', '音频', '图片', '文档', '扩展', '其他'];
    let activeCategoryIndex = 0;
    const categoryBar = document.createElement('div');
    Object.assign(categoryBar.style, { display: 'flex', overflowX: 'auto', padding: '10px', gap: '10px', marginBottom: '10px' });
    const style = document.createElement('style');
    style.innerHTML = `
        .category-bar { display: flex; flex-wrap: nowrap; overflow-x: scroll !important; overflow-y: hidden !important; white-space: nowrap; width: 100%; }
        .category-bar::-webkit-scrollbar { width: 0 !important; height: 0 !important; }
        html, body { height: 100%; margin: 0; padding: 0; overflow: auto !important; }
        video, audio { overflow: hidden !important; }
    `;
    document.head.appendChild(style);
    categories.forEach((cat, index) => {
        const btn = document.createElement('button');
        btn.innerText = cat;
        btn.dataset.index = index;
        Object.assign(btn.style, { padding: '8px 12px', border: 'none', borderRadius: '20px', backgroundColor: index === 0 ? '#007aff' : '#f0f0f0', color: index === 0 ? '#fff' : '#000', cursor: 'pointer', fontSize: '14px', fontWeight: 'normal', whiteSpace: 'nowrap', minWidth: '70px', height: '30px', display: 'flex', justifyContent: 'center', alignItems: 'center' });
        btn.onclick = (e) => { activeCategoryIndex = parseInt(e.target.dataset.index); filterResources(cat); };
        categoryBar.appendChild(btn);
    });
    const resourceList = document.createElement('div');
    Object.assign(resourceList.style, { flex: 1, overflowY: 'auto', padding: '10px' });
    modal.appendChild(modalHeader);
    modal.appendChild(categoryBar);
    modal.appendChild(resourceList);
    document.body.appendChild(snifferBtn);
    document.body.appendChild(modal);
    snifferBtn.onclick = () => { updateResourceList(); modal.style.display = 'flex'; snifferBtn.style.display = 'none'; };
    let allResources = [];
    function updateResourceList() {
        allResources = [];
        document.querySelectorAll('a, source, link, video, audio').forEach(el => {
            const url = el.href || el.src;
            if (url) {
                let type = '其他';
                if (url.match(/\.(mp4|avi|mov|mkv|webm|flv|wmv|mpeg|mpg|3gp|ts|rm|rmvb|ogv|asf|vob|f4v|m4v|qt|h264|hevc|mpeg-2|mpeg-4|divx|xvid|mov|mts|tp|dat|yuv|m3u8|mkv|webm|hls|mpd|dash|ts|f4v)$/)) type = '视频';
                else if (url.match(/\.(mp3|wav|aac|flac|ogg|wma|m4a|opus|alac|ape|dsd|pcm|aiff|au|mid|midi|amr|caf|voc|ra|rm|mpc|tta)$/)) type = '音频';
                else if (url.match(/\.(jpeg|jpg|png|gif|bmp|tiff|tif|webp|heif|heic|jpeg 2000|jp2|j2k|apng|tga|tpic|dds|exr|hdr|raw|svg|ai|eps|pdf|cdr|fig|skp|psd|xcf|ico|icns|stl|obj|ply|dicom|shp|pcx|pict|pct|iff|jbig|jbg|sgi)$/)) type = '图片';
                else if (url.match(/\.(.py|.js|.java|.c|.cpp|.cc|.cxx|.cs|.go|.rb|.php|.swift|.kt|.kts|.ts|.html|.htm|.css|.json|.xml|.sql|.r|.m|.sh|.bash|.ps1|.rs|.lua|.hs|.scala|.tex|.md|.vhd|.vhdl|.asm|.pl|.dart|.el|.erl|.ex|.exs|.jl|.lisp|.ml|.nim|.pas|.pde|.psm1|.rpy|.sml|.tcl|.v|.vbs|.wsf|.yml|.yaml|.coffee|.graphql|.sh|.sql|.scss|.rmd|.styl|.pug|.vue|.handlebars|.twig|.hbs|.asp|.aspx|.cgi|.pl|.psd|.ai|.indd|.abap|.actionscript|.ada|.awk|.batch|.bc|.bh|.bzl|.capnp|.clj|.cljc|.cobol|.coffee|.cql|.cshtml|.cu|.d|.dats|.db|.dcm|.dif|.dtd|.dylib|.f|.f90|.fd|.fxml|.glsl|.h|.hxx|.hpp|.hx|.idl|.inl|.install|.java|.jl|.l|.lisp|.liquid|.lua|.m4|.makefile|.map|.maven|.ml|.mli|.nim|.ninja|.nvm|.objc|.pl|.pm|.ps|.puml|.py|.q|.r|.rexx|.rst|.rs|.scala|.scm|.sh|.shtml|.sml|.sol|.ss|.svg|.tcl|.tex|.ts|.tsx|.v|.vhdl|.vim|.xhtml|.xml|.xsl|.yaml|.yml)$/)) type = '扩展';
                else if (url.match(/\.(pdf|doc|docx|ppt|pptx|xls|xlsx|txt|rtf|odt|ods|odp|epub|mobi|azw3|chm|djvu|tex|md|html|xps|pages|key|numbers|csv|tsv|epub3|fb2|azw|abw)$/)) type = '文档';
                allResources.push({ name: formatName(url), url, type });
            }
        });
        filterResources(categories[activeCategoryIndex]);
    }
    function filterResources(category) {
        resourceList.innerHTML = '';
        const filtered = category === '全部' ? allResources : allResources.filter(res => res.type === category);
        if (filtered.length === 0) { resourceList.innerHTML = `<p style="color:#666; text-align:center;">暂无资源可供下载</p>`; } else {
            filtered.forEach(res => {
                if (!res.name) return;
                const item = document.createElement('div');
                Object.assign(item.style, { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px', fontWeight: 'normal' });
                const name = document.createElement('span');
                name.innerText = res.name;
                name.style.flex = '1';
                name.style.textAlign = 'left';
                name.style.color = '#000';
                name.style.fontWeight = 'normal';
                const downloadBtn = document.createElement('button');
                downloadBtn.innerText = '下载';
                Object.assign(downloadBtn.style, { padding: '6px 12px', border: 'none', borderRadius: '15px', backgroundColor: '#007aff', color: '#fff', cursor: 'pointer', fontSize: '14px', fontWeight: 'bold', width: '70px', height: '30px', display: 'flex', justifyContent: 'center', alignItems: 'center' });
                downloadBtn.onclick = () => downloadResource(res.url, res.name);
                item.appendChild(name);
                item.appendChild(downloadBtn);
                resourceList.appendChild(item);
            });
        }
        categoryBar.querySelectorAll('button').forEach((btn, index) => {
            btn.style.backgroundColor = index === activeCategoryIndex ? '#007aff' : '#f0f0f0';
            btn.style.color = index === activeCategoryIndex ? '#fff' : '#000';
            btn.style.fontSize = '14px';
        });
        setTimeout(() => { categoryBar.scrollLeft = categoryBar.children[activeCategoryIndex].offsetLeft - 20; }, 0);
    }
    function downloadResource(url, filename) {
        if (url.endsWith('.m3u8')) { downloadM3U8(url, filename); return; }
        fetch(url).then(response => { const contentType = response.headers.get('content-type') || ''; return response.blob().then(blob => ({ blob, contentType })); })
            .then(({ blob, contentType }) => {
                const a = document.createElement('a');
                a.href = URL.createObjectURL(blob);
                const forcedExtensions = ['.md', '.json', '.xml', '.csv', '.yaml', '.yml', '.sql', '.sh', '.py', '.js', '.ts', '.html', '.css', '.jsx', '.tsx', '.c', '.cpp', '.java', '.go', '.rb', '.php', '.swift', '.kt', '.r', '.lua', '.pl', '.dart', '.scala', '.vhd', '.asm', '.ps1'];
                const extIndex = filename.lastIndexOf('.');
                let ext = extIndex !== -1 ? filename.slice(extIndex).toLowerCase() : '';
                const randomNum = Math.floor(100 + Math.random() * 900);
                if (!ext) { ext = '.txt'; filename += ext; }
                if (!filename.trim()) { filename = `downloaded_file${randomNum}.txt`; }
                if (forcedExtensions.includes(ext) || contentType.startsWith('text/')) { a.download = filename; } else { a.download = filename; }
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
            })
            .catch(() => alert('资源下载失败'));
    }
    function downloadM3U8(url, filename) {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.responseType = 'blob';
        xhr.onload = () => {
            const contentType = xhr.getResponseHeader('Content-Type') || '';
            if (xhr.status === 200 && contentType.includes('application/vnd.apple.mpegurl')) {
                const a = document.createElement('a');
                a.href = URL.createObjectURL(xhr.response);
                a.download = filename.endsWith('.m3u8') ? filename : `${filename}.m3u8`;
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
            } else {
                alert('M3U8 文件下载失败');
            }
        };
        xhr.send();
    }
    function formatName(url) {
        const parts = url.split('/');
        return parts.length > 0 ? decodeURIComponent(parts[parts.length - 1]) : '';
    }
})();