JGY
「创建博客」 「编辑」 「本文源码」

嗅探脚本


// ==UserScript== // @name 布局修正·UI美化·Bug6.9日修复完整版 // @namespace https://viayoo.com/ // @version 6.3-stable // @description 精准修复顶部分类标签拥挤换行、布局错位,修复代码报错,保留全部原有功能 // @match :///* // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_xmlhttpRequest // @grant GM_download // @grant GM_setClipboard // @grant GM_openInTab // @connect * // @run-at document-end // @exclude ://123apps.com/ // @exclude ://.123apps.com/* // @exclude ://online-audio-converter.com/cn/ // @exclude ://video-converter.com/cn/ // @exclude ://online-video-cutter.com/cn/ // @exclude ://www.diancigaoshou.com/ // ==/UserScript==

(function() { ‘use strict’;

const fixedFontStyle = document.createElement(‘style’); fixedFontStyle.textContent = ` /* 固定浮层:排除 tm- + 三方自身两个弹窗 / div[style=”position:fixed”]:not([class^=”tm-“]):not(#filterHidePanel):not(.mask), div[style=”position:fixed”]:not([class^=”tm-“]):not(#filterHidePanel):not(.mask) *, / 定位浮层:同样排除自身弹窗 / div[style=”top:”][style=”left:”]:not([class^=”tm-“]):not(#filterHidePanel):not(.mask), div[style=”top:”][style=”left:”]:not([class^=”tm-“]):not(#filterHidePanel):not(.mask) *, / 格式过滤设置面板 / .format-settings-header, .format-settings-header *, .format-settings-title, .format-settings-close, .format-group-control-btn, .format-settings-group, .format-settings-group *, .format-settings-group-title, .format-group-controls, .format-checkbox-container, .format-checkbox-container *, .format-checkbox-item, .size-filter-section, .size-filter-section *, .switch-row, .switch-label, .size-filter-row, .size-filter-label, .size-filter-input, .switch, .switch *, / 常规列表面板 / .p-item-url, .p-item-bar, .p-cp, .p-open, .p-down, .p-conv, .p-item-bar *, / 全局span:排除tm组件 + 三方新弹窗内span / span[style=”font-size:11px”]:not(.tm-floating-btn span):not(.tm-mobile-menu span):not(.tm-highlight span):not(#filterHidePanel span):not(.mask span) { font-size: 10px !important; } `; document.head.appendChild(fixedFontStyle);

// 下面是原有代码 let filterCountNum = 0; let invalidLinkCount = 0;

const mgmapi = {
    async getValue(name, defaultVal) {
        return typeof GM_getValue === "function"
            ? GM_getValue(name, defaultVal)
            : await GM.getValue(name, defaultVal);
    },
    async setValue(name, value) {
        return typeof GM_setValue === "function"
            ? GM_setValue(name, value)
            : await GM.setValue(name, value);
    },
    openInTab(url) {
        if (typeof GM_openInTab === "function") {
            GM_openInTab(url, { active: true });
        } else if (typeof GM.openInTab === "function") {
            GM.openInTab(url, { active: true });
        } else {
            window.open(url, '_blank');
        }
    },
    copyText(text) {
        try {
            if (typeof GM_setClipboard === "function") {
                GM_setClipboard(text);
                msg("✅ 复制成功");
                return;
            }
            if (typeof GM.setClipboard === "function") {
                GM.setClipboard(text);
                msg("✅ 复制成功");
                return;
            }
            if (navigator.clipboard) {
                navigator.clipboard.writeText(text).then(() => msg("✅ 复制成功"));
                return;
            }
            const t = document.createElement("textarea");
            t.value = text;
            t.style.opacity = "0";
            document.body.appendChild(t);
            t.select();
            document.execCommand("copy");
            t.remove();
            msg("✅ 复制成功");
        } catch (e) {
            msg("❌ 复制失败");
        }
    },
    downloadFile({ url, name }) {
        if (typeof GM_download === "function") {
            GM_download({ url, name, saveAs: true });
            return;
        }
        const a = document.createElement('a');
        a.href = url;
        a.download = name || url.split('/').pop();
        a.style.display = 'none';
        document.body.appendChild(a);
        a.click();
        setTimeout(() => a.remove(), 100);
    },
    xmlhttpRequest(options) {
        if (typeof GM_xmlhttpRequest === "function") {
            return GM_xmlhttpRequest(options);
        } else if (typeof GM.xmlHttpRequest === "function") {
            return GM.xmlHttpRequest(options);
        } else {
            return new Promise(resolve => {
                if (options.method === "HEAD") {
                    fetch(options.url, { method: "HEAD", mode: 'no-cors' })
                        .then(res => {
                            const headers = new Map();
                            res.headers.forEach((value, key) => headers.set(key.toLowerCase(), value));
                            const contentLength = headers.get('content-length');
                            resolve({
                                responseHeaders: contentLength ? `content-length: ${contentLength}` : '',
                                status: res.status
                            });
                        })
                        .catch(() => resolve({ responseHeaders: '', status: 0 }));
                } else {
                    fetch(options.url)
                        .then(res => res.text())
                        .then(text => resolve({ responseText: text }))
                        .catch(() => resolve({ responseText: '' }));
                }
            });
        }
    }
};

function fixUrl(url) {
    if (!url) return url;
    let u = url.replace(/^file:\/\/\/([^\/]+)\//i, 'https://$1/');
    u = u.split('?')[0].split('#')[0];
    return u;
}

function msg(text, t = 2000) {
let m = document.createElement("div");
m.innerText = text;
m.style = `position:fixed;bottom:60px;right:15px;background:rgba(0,0,0,0.75);color:#fff !important;padding:8px 14px;border-radius:8px;z-index:99999999;font-size:12px !important;backdrop-filter:blur(6px);border:1px solid rgba(255,255,255,0.1);`;
document.body.appendChild(m);
setTimeout(() => m.remove(), t); }



if (location.host === "tools.thatwind.com") return;

let shownUrls = new Set();
let allItems = [];
let currentFilter = 'all';
let sn = 1;
let formatSettingsPanel = null;
let isFormatSettingsOpen = false;
let filteredHideList = [];
let observerLock = false;
let filteredUrlSet = new Set();
let formatCounts = {
    MP4: 0, M3U8: 0, TS: 0, FLV: 0, MKV: 0, WEBM: 0, AVI: 0, MOV: 0, WMV: 0, VIDEO: 0,
    AAC: 0, MP3: 0, AUDIO: 0,
    JPG: 0, PNG: 0, GIF: 0, BMP: 0, WEBP: 0, SVG: 0, ICO: 0, TIFF: 0,
    HEIC: 0, AVIF: 0, JFIF: 0, PJPEG: 0, JXR: 0, IMAGE: 0,
    PDF: 0, ZIP: 0, RAR: 0, APK: 0, EXE: 0, OTHER: 0
};

let enabledFormats = {};
Object.keys(formatCounts).forEach(k => enabledFormats[k] = true);

let sizeFilterSettings = { enabled: false, minSize: 0, maxSize: 0 };
let checkedElements = new WeakSet();
let box, container, categoryBar, list, formatCountDisplay;

// ===== 全局样式(原样保留)=====
const globalStyle = document.createElement('style'); globalStyle.textContent = ` @keyframes categoryVideo {
0%   { box-shadow: 0 0 0 0 rgba(82,196,26,0.8), 0 0 0 0 rgba(255,255,255,0.6); }
50%  { box-shadow: 0 0 14px 6px rgba(82,196,26,0.4), 0 0 20px 8px rgba(255,255,255,0.35); }
100% { box-shadow: 0 0 0 0 rgba(82,196,26,0.8), 0 0 0 0 rgba(255,255,255,0.6); } } @keyframes categoryAudio {
0%   { box-shadow: 0 0 0 0 rgba(64,169,255,0.8), 0 0 0 0 rgba(255,255,255,0.6); }
50%  { box-shadow: 0 0 14px 6px rgba(64,169,255,0.4), 0 0 20px 8px rgba(255,255,255,0.35); }
100% { box-shadow: 0 0 0 0 rgba(64,169,255,0.8), 0 0 0 0 rgba(255,255,255,0.6); } } @keyframes categoryImage {
0%   { box-shadow: 0 0 0 0 rgba(247,89,171,0.8), 0 0 0 0 rgba(255,255,255,0.6); }
50%  { box-shadow: 0 0 14px 6px rgba(247,89,171,0.4), 0 0 20px 8px rgba(255,255,255,0.35); }
100% { box-shadow: 0 0 0 0 rgba(247,89,171,0.8), 0 0 0 0 rgba(255,255,255,0.6); } } @keyframes categoryDoc {
0%   { box-shadow: 0 0 0 0 rgba(250,84,28,0.8), 0 0 0 0 rgba(255,255,255,0.6); }
50%  { box-shadow: 0 0 14px 6px rgba(250,84,28,0.4), 0 0 20px 8px rgba(255,255,255,0.35); }
100% { box-shadow: 0 0 0 0 rgba(250,84,28,0.8), 0 0 0 0 rgba(255,255,255,0.6); } } @keyframes categoryArchive {
0%   { box-shadow: 0 0 0 0 rgba(19,194,194,0.8), 0 0 0 0 rgba(255,255,255,0.6); }
50%  { box-shadow: 0 0 14px 6px rgba(19,194,194,0.4), 0 0 20px 8px rgba(255,255,255,0.35); }
100% { box-shadow: 0 0 0 0 rgba(19,194,194,0.8), 0 0 0 0 rgba(255,255,255,0.6); } } @keyframes categoryApp {
0%   { box-shadow: 0 0 0 0 rgba(235,47,150,0.8), 0 0 0 0 rgba(255,255,255,0.6); }
50%  { box-shadow: 0 0 14px 6px rgba(235,47,150,0.4), 0 0 20px 8px rgba(255,255,255,0.35); }
100% { box-shadow: 0 0 0 0 rgba(235,47,150,0.8), 0 0 0 0 rgba(255,255,255,0.6); } } @keyframes tabAllRainbow {
0%   { box-shadow: 0 0 10px 4px hsla(0,80%,60%,0.6), 0 0 16px 6px rgba(255,255,255,0.5); }
25%  { box-shadow: 0 0 10px 4px hsla(90,80%,55%,0.6), 0 0 16px 6px rgba(255,255,255,0.5); }
50%  { box-shadow: 0 0 10px 4px hsla(180,80%,60%,0.6), 0 0 16px 6px rgba(255,255,255,0.5); }
75%  { box-shadow: 0 0 10px 4px hsla(270,80%,65%,0.6), 0 0 16px 6px rgba(255,255,255,0.5); }
100% { box-shadow: 0 0 10px 4px hsla(360,80%,60%,0.6), 0 0 16px 6px rgba(255,255,255,0.5); } } .clear-animate-btn.run-ani{
animation: tabAllRainbow 3s ease-in-out infinite !important; } .clear-animate-btn{
animation: none !important; }

@keyframes ballRainbowGlow { 0% { box-shadow: 0 0 0 0 hsla(0, 80%, 60%, 0.8), 0 0 0 0 rgba(255,255,255,0.6); } 14% { box-shadow: 0 0 16px 6px hsla(45, 80%, 60%, 0.5), 0 0 24px 10px rgba(255,255,255,0.35); } 28% { box-shadow: 0 0 16px 6px hsla(90, 80%, 55%, 0.5), 0 0 24px 10px rgba(255,255,255,0.35); } 42% { box-shadow: 0 0 16px 6px hsla(135, 80%, 55%, 0.5), 0 0 24px 10px rgba(255,255,255,0.35); } 56% { box-shadow: 0 0 16px 6px hsla(180, 80%, 60%, 0.5), 0 0 24px 10px rgba(255,255,255,0.35); } 70% { box-shadow: 0 0 16px 6px hsla(225, 80%, 65%, 0.5), 0 0 24px 10px rgba(255,255,255,0.35); } 84% { box-shadow: 0 0 16px 6px hsla(270, 80%, 65%, 0.5), 0 0 24px 10px rgba(255,255,255,0.35); } 100% { box-shadow: 0 0 0 0 hsla(360, 80%, 60%, 0.8), 0 0 0 0 rgba(255,255,255,0.6); } } @keyframes ballRainbowGlowActive { 0% { box-shadow: 0 0 0 0 hsla(0, 80%, 60%, 0.8), 0 0 0 0 rgba(255,255,255,0.6); } 14% { box-shadow: 0 0 16px 6px hsla(45, 80%, 60%, 0.6), 0 0 24px 10px rgba(255,255,255,0.45); } 28% { box-shadow: 0 0 16px 6px hsla(90, 80%, 55%, 0.6), 0 0 24px 10px rgba(255,255,255,0.45); } 42% { box-shadow: 0 0 16px 6px hsla(135, 80%, 55%, 0.6), 0 0 24px 10px rgba(255,255,255,0.45); } 56% { box-shadow: 0 0 16px 6px hsla(180, 80%, 60%, 0.6), 0 0 24px 10px rgba(255,255,255,0.45); } 70% { box-shadow: 0 0 16px 6px hsla(225, 80%, 65%, 0.6), 0 0 24px 10px rgba(255,255,255,0.45); } 84% { box-shadow: 0 0 16px 6px hsla(270, 80%, 65%, 0.6), 0 0 24px 10px rgba(255,255,255,0.45); } 100% { box-shadow: 0 0 0 0 hsla(360, 80%, 60%, 0.8), 0 0 0 0 rgba(255,255,255,0.6); } } .icon{ position:fixed;z-index:2147483647 !important; width:52px;height:52px;border-radius:50%; background:rgba(0,0,0,0.8);display:flex; align-items:center;justify-content:center; font-size:11px !important;color:#fff !important;font-weight:bold !important; cursor:move; animation: ballRainbowGlow 3.2s ease-in-out infinite !important; right: 10px; top: 50%; transform: translateY(-50%); opacity: 0.8; transition: all 0.3s cubic-bezier(0.25,0.8,0.25,1) !important; }

.icon.active{animation: ballRainbowGlowActive 1.6s ease-in-out infinite !important;}

.circular-ring{ position: absolute; top: 0; left: 0; width: 100%; height: 100%; border-radius: 50%; pointer-events: none; z-index: 0 !important; transform-origin: center; animation: starRotate 8s linear infinite !important; } @keyframes starRotate{ 0%{transform:rotate(0deg)} 100%{transform:rotate(360deg)} } .star-item{ position: absolute; left: 50%; top: 0; font-size: 11px !important; opacity: 0.6; transform-origin: center 158px; } @keyframes starTwinkle{ 0%,100%{opacity:0.3;transform:scale(0.7)} 50%{opacity:1;transform:scale(1.3)} } .star-item.video{color:#52c41a !important;} .star-item.audio{color:#722ed1 !important;} .star-item.image{color:#f759ab !important;} .star-item.doc{color:#fa541c !important;} .star-item.zip{color:#13c2c2 !important;}

.outer-ring{ position: absolute; left: 0; top: 0; width: 100%; height: 100%; border-radius: 50%; pointer-events: none; z-index: 1 !important; }

@keyframes ring-vid{ 0%{transform:scale(2.0);opacity:1;border:3px solid #52c41a;} 100%{transform:scale(1);opacity:0;border:3px solid #52c41a;} } @keyframes ring-aud{ 0%{transform:scale(2.0);opacity:1;border:3px solid #722ed1;} 100%{transform:scale(1);opacity:0;border:3px solid #722ed1;} } @keyframes ring-img{ 0%{transform:scale(2.0);opacity:1;border:3px solid #f759ab;} 100%{transform:scale(1);opacity:0;border:3px solid #f759ab;} } @keyframes ring-doc{ 0%{transform:scale(2.0);opacity:1;border:3px solid #fa541c;} 100%{transform:scale(1);opacity:0;border:3px solid #fa541c;} } @keyframes ring-zip{ 0%{transform:scale(2.0);opacity:1;border:3px solid #13c2c2;} 100%{transform:scale(1);opacity:0;border:3px solid #13c2c2;} }

.ring-vid{animation:ring-vid 1.4s ease forwards;} .ring-aud{animation:ring-aud 1.4s ease forwards;} .ring-img{animation:ring-img 1.4s ease forwards;} .ring-doc{animation:ring-doc 1.4s ease forwards;} .ring-zip{animation:ring-zip 1.4s ease forwards;}

.category-tab { transition: box-shadow .3s ease; padding:4px 8px;border-radius:16px;font-size:11px;cursor:pointer; transition: all 0.25s ease;white-space:nowrap;border:1px solid transparent; opacity:0.85;flex:0 0 auto; } .category-tab.active[data-category=”all”]{ animation: tabAllRainbow 3s ease-in-out infinite !important; } .category-tab.active[data-category=”video”]{ animation: categoryVideo 2.6s ease-in-out infinite !important; } .category-tab.active[data-category=”audio”]{ animation: categoryAudio 2.6s ease-in-out infinite !important; } .category-tab.active[data-category=”image”]{ animation: categoryImage 2.6s ease-in-out infinite !important; } .category-tab.active[data-category=”document”]{ animation: categoryDoc 2.6s ease-in-out infinite !important; } .category-tab.active[data-category=”archive”]{ animation: categoryArchive 2.6s ease-in-out infinite !important; } .category-tab.active[data-category=”app”]{ animation: categoryApp 2.6s ease-in-out infinite !important; } .category-tab.active[data-category=”other”]{ animation: categoryDoc 2.6s ease-in-out infinite !important; }

.category-row-wrap { display:flex; gap:4px; row-gap:6px; padding:4px 8px 0 8px; flex-wrap: wrap; align-items:center; justify-content:flex-start; overflow: hidden; flex-shrink: 0; }

.item .cp,.item .open-btn,.item .download-btn,.item .convert-btn { transition: all .15s ease; border-radius:6px; } .item .cp:hover,.item .open-btn:hover,.item .download-btn:hover,.item .convert-btn:hover { transform: scale(1.05); filter: brightness(1.2); background:rgba(255,255,255,0.15) !important; } ::-webkit-scrollbar{width:6px;height:6px;} ::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.25);border-radius:6px;} ::-webkit-scrollbar-track{background:rgba(255,255,255,0.05);} .switch { position:relative;display:inline-block;width:44px;height:24px; } .switch input {opacity:0;width:0;height:0;} .slider { position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0; background-color:#2a2a2a;transition:.3s;border-radius:24px; border:1px solid rgba(255,255,255,0.1); } .slider:before { position:absolute;content:”“;height:18px;width:18px;left:2px;bottom:2px; background-color:#fff;transition:.3s;border-radius:50%; } input:checked + .slider {background-color:#40a9ff;} input:checked + .slider:before {transform: translateX(20px);} `; document.head.appendChild(globalStyle);

// ===== 弹窗样式(原样保留)=====
const popStyle = document.createElement('style');
popStyle.textContent = ` #filterHidePanel{
position:fixed;
top:50%;left:50%;
transform:translate(-50%,-50%);
width:85%;max-width:800px;max-height:75vh;
background:rgba(0,0,0,0.75);
border-radius:12px;border:1px solid rgba(255,255,255,0.15);
z-index:99999999;display:none;flex-direction:column;overflow:hidden; } #filterHidePanel .p-head{ padding:12px 18px;background:rgba(0,0,0,0.6); display:flex;justify-content:space-between;align-items:center; color:#fff;font-size:14px;border-bottom:1px solid rgba(255,255,255,0.1); position: sticky;top: 0;z-index: 100; } #filterHidePanel .p-close{cursor:pointer;font-size:20px;color:#ccc;padding:0 8px;transition:color 0.2s;} #filterHidePanel .p-close:hover{color:#fff;} #filterHidePanel .p-list{flex:1;padding:10px;overflow-y:auto;} #filterHidePanel .p-item{ background:rgba(255,255,255,0.05);color:#fff;padding:12px;border-radius:10px;margin:6px 0;font-size:12px; border-left:3px solid #40a9ff;transition:background 0.2s; } #filterHidePanel .p-item:hover{background:rgba(255,255,255,0.08);} #filterHidePanel .p-item-url{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:4px 0;color:#eee;line-height:1.4;} #filterHidePanel .p-item-bar{display:flex;gap:12px;margin-top:10px;flex-wrap:wrap;} #filterHidePanel .p-cp,#filterHidePanel .p-open,#filterHidePanel .p-down,#filterHidePanel .p-conv{cursor:pointer;padding:3px 8px;border:1px solid;border-radius:6px;font-size:11px;transition:all 0.2s;} #filterHidePanel .p-cp{color:#40a9ff;border-color:#40a9ff;} #filterHidePanel .p-open{color:#722ed1;border-color:#722ed1;} #filterHidePanel .p-down{color:#52c41a;border-color:#52c41a;} #filterHidePanel .p-conv{color:#fa8c16;border-color:#fa8c16;} #filterHidePanel .p-cp:hover,#filterHidePanel .p-open:hover,#filterHidePanel .p-down:hover,#filterHidePanel .p-conv:hover{background:rgba(255,255,255,0.15);} `;
document.head.appendChild(popStyle);

// ===== 面板逻辑(略,保持你原有结构)=====
function createHideFilterPanel() {
if (document.getElementById('filterHidePanel')) return;

// 注入专属样式,锁定字号
const style = document.createElement('style');
style.textContent = `
    #filterHidePanel .p-head {
        font-size: 16px !important;
        line-height: 1.5 !important;
    }
    #filterHidePanel .p-head span {
        font-size: inherit !important;
    }
    #filterHidePanel .p-close {
        font-size: 18px !important;
        cursor: pointer;
    }
`;
document.head.appendChild(style);

const p = document.createElement('div');
p.id = 'filterHidePanel';
p.innerHTML = `
    <div class="p-head">
        <span>已被过滤资源列表</span>
        <span class="p-close">×</span>
    </div>
    <div id="hideFilterListWrap" class="p-list"></div>
`;
document.body.appendChild(p);
p.querySelector('.p-close').onclick = () => p.style.display = 'none';
p.onclick = e => { if (e.target === p) p.style.display = 'none'; }; }


function showHideFilterPanel() {
    createHideFilterPanel();
    const panel = document.getElementById('filterHidePanel');
    const wrap = document.getElementById('hideFilterListWrap');

    // 先提前注入样式(放在脚本顶部/初始化位置) const style = document.createElement('style'); style.textContent = ` .empty-tip {
color: #888 !important;
text-align: center !important;
padding: 30px !important;
font-size: 13px !important; } `; document.head.appendChild(style);

// 对应逻辑改为 if (filteredHideList.length === 0) { wrap.innerHTML = ‘<div class="empty-tip">暂无被过滤的资源</div>’;

    } else {
        let html = '';
        filteredHideList.forEach((item, idx) => {
const shortUrl = item.url.length > 120 ? item.url.substring(0, 120) + '...' : item.url;
let typeClass = 'type-default';
const cat = getFileCategory(item.type);
if (cat === 'video') typeClass = 'type-video';
else if (cat === 'audio') typeClass = 'type-audio';
else if (cat === 'image') typeClass = 'type-image';
else if (cat === 'document') typeClass = 'type-document';
else if (cat === 'archive') typeClass = 'type-archive';
else if (cat === 'app') typeClass = 'type-app';

html += `<div class="p-item">
${idx + 1}. ${item.type} ${item.info || '已过滤'}
${shortUrl}
复制 新标签打开 下载 转换

</div>`;

});

        wrap.innerHTML = html;
    }

    panel.style.display = 'flex';

    setTimeout(() => {
        wrap.querySelectorAll('.p-cp').forEach(el => el.onclick = () => mgmapi.copyText(el.dataset.url));
        wrap.querySelectorAll('.p-open').forEach(el => el.onclick = () => mgmapi.openInTab(el.dataset.url));
        wrap.querySelectorAll('.p-down').forEach(el => el.onclick = function () {
            mgmapi.downloadFile({ url: this.dataset.url, name: this.dataset.name });
        });
        wrap.querySelectorAll('.p-conv').forEach(el => el.onclick = () => handleConversion(el.dataset.url));
    }, 50);
}

function getFileType(url) {
    if (!url || typeof url !== 'string') return "OTHER";
    const urlLower = url.toLowerCase();
    if (/\.webp(\?|$|#|&|%)/i.test(url)) return "WEBP";
    if (urlLower.includes('/webp/') || urlLower.includes('/format,webp/') || urlLower.includes('format=webp') || urlLower.includes('fm=webp') || urlLower.includes('f=webp') || urlLower.includes('type=webp')) return "WEBP";
    if (/\.mp4(\?|$|#)/i.test(url)) return "MP4";
    if (/\.m3u8(\?|$|#)/i.test(url)) return "M3U8";
    if (/\.ts(\?|$|#)/i.test(url)) return "TS";
    if (/\.aac(\?|$|#)/i.test(url)) return "AAC";
    if (/\.mp3(\?|$|#)/i.test(url)) return "MP3";
    if (/\.flv(\?|$|#)/i.test(url)) return "FLV";
    if (/\.mkv(\?|$|#)/i.test(url)) return "MKV";
    if (/\.webm(\?|$|#)/i.test(url)) return "WEBM";
    if (/\.avi(\?|$|#)/i.test(url)) return "AVI";
    if (/\.mov(\?|$|#)/i.test(url)) return "MOV";
    if (/\.wmv(\?|$|#)/i.test(url)) return "WMV";
    if (/\.jpg(\?|$|#)/i.test(url) || /\.jpeg(\?|$|#)/i.test(url)) return "JPG";
    if (/\.png(\?|$|#)/i.test(url)) return "PNG";
    if (/\.gif(\?|$|#)/i.test(url)) return "GIF";
    if (/\.bmp(\?|$|#)/i.test(url)) return "BMP";
    if (/\.svg(\?|$|#)/i.test(url)) return "SVG";
    if (/\.ico(\?|$|#)/i.test(url)) return "ICO";
    if (/\.tiff(\?|$|#)/i.test(url) || /\.tif(\?|$|#)/i.test(url)) return "TIFF";
    if (/\.heic(\?|$|#)/i.test(url) || /\.heif(\?|$|#)/i.test(url)) return "HEIC";
    if (/\.avif(\?|$|#)/i.test(url)) return "AVIF";
    if (/\.jfif(\?|$|#)/i.test(url)) return "JFIF";
    if (/\.pjpeg(\?|$|#)/i.test(url) || /\.pjpg(\?|$|#)/i.test(url)) return "PJPEG";
    if (/\.jxr(\?|$|#)/i.test(url) || /\.hdp(\?|$|#)/i.test(url) || /\.wdp(\?|$|#)/i.test(url)) return "JXR";
    if (/\.pdf(\?|$|#)/i.test(url)) return "PDF";
    if (/\.zip(\?|$|#)/i.test(url)) return "ZIP";
    if (/\.rar(\?|$|#)/i.test(url)) return "RAR";
    if (/\.apk(\?|$|#)/i.test(url)) return "APK";
    if (/\.exe(\?|$|#)/i.test(url)) return "EXE";
    if (urlLower.includes('/video/') || urlLower.includes('type=video') || urlLower.includes('video/')) return "VIDEO";
    if (urlLower.includes('/audio/') || urlLower.includes('type=audio') || urlLower.includes('audio/')) return "AUDIO";
    if (urlLower.includes('/image/') || urlLower.includes('type=image') || urlLower.includes('image/')) {
        if (urlLower.includes('jpeg') || urlLower.includes('jpg')) return "JPG";
        if (urlLower.includes('png')) return "PNG";
        if (urlLower.includes('gif')) return "GIF";
        if (urlLower.includes('webp')) return "WEBP";
        if (urlLower.includes('svg')) return "SVG";
        if (urlLower.includes('bmp')) return "BMP";
        if (urlLower.includes('ico')) return "ICO";
        if (urlLower.includes('tiff') || urlLower.includes('tif')) return "TIFF";
        if (urlLower.includes('heic') || urlLower.includes('heif')) return "HEIC";
        if (urlLower.includes('avif')) return "AVIF";
        return "IMAGE";
    }
    return "OTHER";
}

function getFileCategory(type) {
    const categories = {
        'video': ['MP4', 'M3U8', 'TS', 'FLV', 'MKV', 'WEBM', 'AVI', 'MOV', 'WMV', 'VIDEO'],
        'audio': ['AAC', 'MP3', 'AUDIO'],
        'image': ['JPG', 'PNG', 'GIF', 'BMP', 'WEBP', 'SVG', 'ICO', 'TIFF', 'HEIC', 'AVIF', 'JFIF', 'PJPEG', 'JXR', 'IMAGE'],
        'document': ['PDF'],
        'archive': ['ZIP', 'RAR'],
        'app': ['APK', 'EXE'],
        'other': ['OTHER']
    };
    for (const [category, types] of Object.entries(categories)) {
        if (types.includes(type)) return category;
    }
    return 'other';
}

function checkSizeFilter(fileSize) {
    if (!sizeFilterSettings.enabled) return true;
    if (fileSize === 0) return true;
    const minSize = sizeFilterSettings.minSize * 1024;
    const maxSize = sizeFilterSettings.maxSize * 1024;
    if (minSize > 0 && fileSize < minSize) return false;
    if (maxSize > 0 && fileSize > maxSize) return false;
    return true;
}

function isInvalidItem(item) {
    if (!item) return false;
    const info = (item.info || "");
    return info.includes("未知大小") || info.includes("跨域无法获取") || info.includes("获取失败") || info.includes("解析失败");
}

async function getInfoByUrl(url, type) {
    let finalUrl = fixUrl(url);
    const imgTypes = ["JPG", "PNG", "GIF", "BMP", "WEBP", "SVG", "ICO", "TIFF", "HEIC", "AVIF", "JFIF", "PJPEG", "JXR", "IMAGE"];

    if (imgTypes.includes(type)) {
        return new Promise(resolve => {
            let fileSize = 0;
            mgmapi.xmlhttpRequest({
                method: "HEAD",
                url: finalUrl,
                timeout: 2500,
                onload(res) {
                    const sizeMat = res.responseHeaders?.match(/content-length:\s*(\d+)/i);
                    if (sizeMat) fileSize = parseInt(sizeMat[1]) || 0;
                },
                onerror() { },
                ontimeout() { getImgSize(); }
            });
            setTimeout(getImgSize, 300);

            function getImgSize() {
                let sizeText = "未知大小";
                if (fileSize > 0) {
                    if (fileSize < 1024) sizeText = fileSize + "B";
                    else if (fileSize < 1024 * 1024) sizeText = (fileSize / 1024).toFixed(1) + "KB";
                    else sizeText = (fileSize / 1024 / 1024).toFixed(1) + "MB";
                }
                const img = new Image();
                img.src = finalUrl;
                img.onload = function () {
                    const w = img.naturalWidth || img.width;
                    const h = img.naturalHeight || img.height;
                    if (w && h && w > 10 && h > 10)
                        resolve({ info: `${sizeText} | 📐 ${w} × ${h}`, size: fileSize, realUrl: finalUrl, isApi: false });
                    else
                        resolve({ info: `${sizeText} | 📐 无法解析尺寸`, size: fileSize, realUrl: finalUrl, isApi: false });
                };
                img.onerror = img.onabort = function () {
                    resolve({ info: `${sizeText} | 📐 跨域无法获取`, size: fileSize, realUrl: finalUrl, isApi: false });
                };
                setTimeout(() => resolve({ info: `${sizeText} | 📐 获取超时`, size: fileSize, realUrl: finalUrl, isApi: false }), 3000);
            }
        });
    }

    return new Promise(resolve => {
        mgmapi.xmlhttpRequest({
            method: "HEAD",
            url: finalUrl,
            timeout: 3000,
            onload(res) {
                if (res.finalUrl) finalUrl = res.finalUrl;
                let isApi = false;
                const ct = res.responseHeaders || "";
                if (/content-type.*xml/i.test(ct) || /content-type.*json/i.test(ct)) isApi = true;
                if (/\.xml(\?|$|#)/i.test(finalUrl) || /\.json(\?|$|#)/i.test(finalUrl)) isApi = true;
                if (isApi) return resolve({ info: "⚠️ 接口XML/JSON", size: 0, realUrl: finalUrl, isApi: true });

                if (["MP4", "TS", "FLV", "MKV", "WEBM", "AVI", "MOV", "WMV", "VIDEO"].includes(type)) {
                    try {
                        const size = res.responseHeaders.match(/content-length:\s*(\d+)/i);
                        const length = size ? parseInt(size[1]) : 0;
                        let infoText = length ? (length < 1024 ? length + "B" : length < 1024 * 1024 ? (length / 1024).toFixed(1) + "KB" : (length / 1024 / 1024).toFixed(1) + "MB") : "未知大小";
                        if (["MP4", "MOV", "AVI", "WMV"].includes(type)) infoText += ` | ⏱ ~${Math.round(length / (1024 * 256))}s`;
                        else if (type === "TS") infoText += ` | ⏱ ~${Math.round(length / (1024 * 128))}s`;
                        else if (type === "FLV") infoText += ` | ⏱ ~${Math.round(length / (1024 * 200))}s`;
                        resolve({ info: infoText, size: length, realUrl: finalUrl, isApi: false });
                    } catch (e) {
                        resolve({ info: "未知大小", size: 0, realUrl: finalUrl, isApi: false });
                    }
                } else if (["MP3", "AAC", "AUDIO"].includes(type)) {
                    try {
                        const size = res.responseHeaders.match(/content-length:\s*(\d+)/i);
                        const length = size ? parseInt(size[1]) : 0;
                        let infoText = length ? (length < 1024 ? length + "B" : length < 1024 * 1024 ? (length / 1024).toFixed(1) + "KB" : (length / 1024 / 1024).toFixed(1) + "MB") : "未知大小";
                        if (type === "MP3") infoText += ` | ⏱ ~${Math.round(length / (1024 * 128))}s`;
                        else if (type === "AAC") infoText += ` | ⏱ ~${Math.round(length / (1024 * 64))}s`;
                        resolve({ info: infoText, size: length, realUrl: finalUrl, isApi: false });
                    } catch (e) {
                        resolve({ info: "未知大小", size: 0, realUrl: finalUrl, isApi: false });
                    }
                } else if (type === "M3U8") {
                    mgmapi.xmlhttpRequest({
                        method: "GET",
                        url: finalUrl,
                        timeout: 5000,
                        onload(res) {
                            try {
                                const text = res.responseText;
                                if (!text || !text.trim().startsWith('#EXTM3U'))
                                    return resolve({ info: "无效M3U8", size: 0, realUrl: finalUrl, isApi: false });
                                const lines = text.split('\n');
                                let tsCount = 0;
                                for (let i = 0; i < lines.length; i++)
                                    if (lines[i].trim() && !lines[i].startsWith('#')) tsCount++;
                                resolve({ info: `分片:${tsCount}`, size: 0, realUrl: finalUrl, isApi: false });
                            } catch (e) {
                                resolve({ info: "解析失败", size: 0, realUrl: finalUrl, isApi: false });
                            }
                        },
                        onerror() {
                            resolve({ info: "获取失败", size: 0, realUrl: finalUrl, isApi: false });
                        }
                    });
                } else {
                    try {
                        const size = res.responseHeaders.match(/content-length:\s*(\d+)/i);
                        const length = size ? parseInt(size[1]) : 0;
                        let infoText = length ? (length < 1024 ? length + "B" : length < 1024 * 1024 ? (length / 1024).toFixed(1) + "KB" : (length / 1024 / 1024).toFixed(1) + "MB") : "未知大小";
                        resolve({ info: infoText, size: length, realUrl: finalUrl, isApi: false });
                    } catch (e) {
                        resolve({ info: "未知大小", size: 0, realUrl: url, isApi: false });
                    }
                }
            },
            onerror() {
                resolve({ info: "未知大小", size: 0, realUrl: url, isApi: false });
            }
        });
    });
}

function initDetect() {
    const win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;

    if (typeof win.fetch === 'function') {
        const originalFetch = win.fetch;
        win.fetch = function (...args) {
            const requestUrl = args[0] && typeof args[0] === 'string' ? args[0] : (args[0] && args[0].url ? args[0].url : args[0]);
            if (requestUrl && typeof requestUrl === 'string') {
                const type = getFileType(requestUrl);
                if (type !== "OTHER" && enabledFormats[type]) addItem({ type, url: requestUrl });
            }
            const fetchPromise = originalFetch.apply(this, args);
            fetchPromise.then(response => {
                if (response && response.url) {
                    const type = getFileType(response.url);
                    if (type !== "OTHER" && enabledFormats[type]) addItem({ type, url: response.url });
                }
                return response;
            }).catch(() => { });
            return fetchPromise;
        };
    }

    if (typeof win.XMLHttpRequest === 'function') {
        const OriginalXHR = win.XMLHttpRequest;
        win.XMLHttpRequest = function () {
            const xhr = new OriginalXHR();
            const originalOpen = xhr.open;
            const originalSend = xhr.send;
            let requestUrl = '';

            xhr.open = function (method, url, ...rest) {
                requestUrl = url;
                return originalOpen.apply(this, [method, url, ...rest]);
            };

            xhr.send = function (data) {
                const onLoad = () => {
                    try {
                        const finalUrl = xhr.responseURL || requestUrl;
                        if (finalUrl) {
                            const type = getFileType(finalUrl);
                            if (type !== "OTHER" && enabledFormats[type]) addItem({ type, url: finalUrl });
                        }
                    } catch (e) { }
                };
                xhr.addEventListener('load', onLoad, { once: true });
                return originalSend.call(this, data);
            };
            return xhr;
        };
    }

    function checkPageResources() {
        if (observerLock) return;
        observerLock = true;
        setTimeout(() => observerLock = false, 500);

        document.querySelectorAll('video').forEach(video => {
            if (video.src && !checkedElements.has(video)) {
                const type = getFileType(video.src);
                if (type !== "OTHER" && enabledFormats[type] && getFileCategory(type) === 'video')
                    addItem({ type, url: video.src });
                checkedElements.add(video);
            }
            video.querySelectorAll('source').forEach(source => {
                if (source.src && !checkedElements.has(source)) {
                    const type = getFileType(source.src);
                    if (type !== "OTHER" && enabledFormats[type] && getFileCategory(type) === 'video')
                        addItem({ type, url: source.src });
                    checkedElements.add(source);
                }
            });
        });

        document.querySelectorAll('audio').forEach(audio => {
            if (audio.src && !checkedElements.has(audio)) {
                const type = getFileType(audio.src);
                if (type !== "OTHER" && enabledFormats[type])
                    addItem({ type, url: audio.src });
                checkedElements.add(audio);
            }
            audio.querySelectorAll('source').forEach(source => {
                if (source.src && !checkedElements.has(source)) {
                    const type = getFileType(source.src);
                    if (type !== "OTHER" && enabledFormats[type])
                        addItem({ type, url: source.src });
                    checkedElements.add(source);
                }
            });
        });

        document.querySelectorAll('img').forEach(img => {
            if (img.src && !checkedElements.has(img)) {
                const type = getFileType(img.src);
                if (["JPG", "PNG", "GIF", "BMP", "WEBP", "SVG", "ICO", "TIFF", "HEIC", "AVIF", "JFIF", "PJPEG", "JXR", "IMAGE"].includes(type) && enabledFormats[type])
                    addItem({ type, url: img.src });
                checkedElements.add(img);
            }
            ['data-original', 'data-actualsrc', 'data-src', 'data-lazy-src'].forEach(attr => {
                const dataUrl = img.getAttribute(attr);
                if (dataUrl && dataUrl.length > 10 && !dataUrl.startsWith('data:') && !checkedElements.has(img)) {
                    const type = getFileType(dataUrl);
                    if (["JPG", "PNG", "GIF", "BMP", "WEBP", "SVG", "ICO", "TIFF", "HEIC", "AVIF", "JFIF", "PJPEG", "JXR", "IMAGE"].includes(type) && enabledFormats[type])
                        addItem({ type, url: dataUrl });
                }
            });
        });

        document.querySelectorAll('picture').forEach(picture => {
            if (!checkedElements.has(picture)) {
                picture.querySelectorAll('source').forEach(source => {
                    if (source.srcset && !checkedElements.has(source)) {
                        const srcset = source.srcset;
                        const sources = srcset.split(',').map(s => s.trim().split(' ')[0]);
                        sources.forEach(src => {
                            if (src) {
                                const type = getFileType(src);
                                if (type !== "OTHER" && enabledFormats[type])
                                    addItem({ type, url: src });
                            }
                        });
                        checkedElements.add(source);
                    }
                });
                checkedElements.add(picture);
            }
        });

        document.querySelectorAll('a').forEach(link => {
            if (link.href && !checkedElements.has(link)) {
                const type = getFileType(link.href);
                if (type !== "OTHER" && enabledFormats[type])
                    addItem({ type, url: link.href });
                checkedElements.add(link);
            }
        });

        document.querySelectorAll('*[style*="url("]').forEach(element => {
            if (!checkedElements.has(element)) {
                const styleText = element.getAttribute('style') || '';
                const urlMatches = styleText.match(/url\(["']?([^"')]+)["']?\)/gi);
                if (urlMatches) {
                    urlMatches.forEach(match => {
                        const url = match.replace(/url\(["']?(.*?)["']?\)/i, '$1').trim();
                        if (url) {
                            const type = getFileType(url);
                            if (type !== "OTHER" && enabledFormats[type])
                                addItem({ type, url });
                        }
                    });
                    checkedElements.add(element);
                }
            }
        });
    }

    if (document.body) checkPageResources();

    if (typeof MutationObserver !== 'undefined') {
        const observer = new MutationObserver(() => setTimeout(checkPageResources, 300));
        if (document.body) observer.observe(document.body, { childList: true, subtree: true });
    }

    if (typeof PerformanceObserver !== 'undefined') {
        try {
            const resourceObserver = new PerformanceObserver(list => {
                list.getEntries().forEach(entry => {
                    if (['fetch', 'xmlhttprequest', 'script', 'link', 'img'].includes(entry.initiatorType)) {
                        const url = entry.name;
                        if (url) {
                            const type = getFileType(url);
                            if (type !== "OTHER" && enabledFormats[type])
                                addItem({ type, url });
                        }
                    }
                });
            });
            resourceObserver.observe({ entryTypes: ['resource'] });
        } catch (e) { }
    }

    ['play', 'loadedmetadata', 'canplay'].forEach(event =>
        document.addEventListener(event, (e) => {
            const target = e.target;
            if (target && (target.tagName === 'VIDEO' || target.tagName === 'AUDIO')) {
                if (target.src && !checkedElements.has(target)) {
                    const type = getFileType(target.src);
                    if (type !== "OTHER" && enabledFormats[type])
                        addItem({ type, url: target.src });
                    checkedElements.add(target);
                }
            }
        }, true)
    );

    setInterval(checkPageResources, 3000);
}

function handleConversion(url) {
    mgmapi.copyText(url);
    setTimeout(() => {
        const conversionUrl = "https://123apps.com/cn/";
        const redirectPage = `<!DOCTYPE html><html><head><title>重定向</title><script>window.onload=function(){window.location.replace("${conversionUrl}");}</script></head><body></body></html>`;
        const dataUrl = 'data:text/html;charset=utf-8,' + encodeURIComponent(redirectPage);
        const newWindow = window.open(dataUrl, '_blank', 'noopener,noreferrer,width=1200,height=700');
        newWindow ? newWindow.focus() : mgmapi.openInTab(conversionUrl);
        msg("✅ 链接已复制,正在打开转换网站...");
    }, 100);
}

async function loadUserSettings() {
    const savedEnabled = await mgmapi.getValue('formatEnabledFormats', null);
    if (savedEnabled) Object.assign(enabledFormats, savedEnabled);
    sizeFilterSettings.enabled = await mgmapi.getValue('sizeFilterEnable', false);
    sizeFilterSettings.minSize = await mgmapi.getValue('sizeFilterMin', 0);
    sizeFilterSettings.maxSize = await mgmapi.getValue('sizeFilterMax', 0);
}

function initUI() {
    if (box) return;

    box = document.createElement("div");
    box.id = "sniffer-main-box";
    box.style = `position:fixed;z-index:9999999;left:15px;top:80px;width:320px;max-width:90vw;display:block;user-select:none;`;
    document.body.appendChild(box);

    const btn = document.createElement("div");
    btn.style = "text-align:right;margin-bottom:8px;";
    btn.innerHTML = `<div class="icon" style="width:52px;height:52px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;position:relative;font-size:11px;color:#fff;font-weight:bold;letter-spacing:1px;cursor:move;">
        <div class="format-display" style="font-size:9px;line-height:1.2;text-align:center;padding:2px;"><div>资源嗅探</div><div style="font-size:8px;color:#aaa;">点击展开</div></div>
        <span class="tipBadge" style="position:absolute;right:-8px;bottom:-6px;background:#f5222d;color:#fff;font-size:9px;border-radius:10px;padding:1px 4px;z-index:99;">0</span>
    </div>`;
    box.appendChild(btn);

    formatCountDisplay = btn.querySelector(".format-display");

    container = document.createElement("div");
    container.style = `max-height:60vh;display:none;flex-direction:column;background:rgba(0,0,0,0.75);-webkit-border:1px solid rgba(255,255,255,0.1);border-radius:8px;overflow:hidden;`;
    box.appendChild(container);

    const categoryRow = document.createElement("div");
    categoryRow.className = "category-row-wrap";
    container.appendChild(categoryRow);
    initCategoryTabs(categoryRow);

    const controlRow = document.createElement("div");
    controlRow.style = "display:flex;gap:8px;justify-content:center;padding:4px 8px;flex-wrap:wrap;background:transparent;border-bottom:none;margin-top:0;";
    controlRow.innerHTML = `
    <div style="display:flex;flex-wrap:nowrap;align-items:center;gap:6px;width:100%;overflow:hidden;justify-content:center;">
        <div class="batch-btn" style="padding:5px 8px;background:#01847e;color:#fff;border-radius:12px;font-size:10px;cursor:pointer;white-space:nowrap;transition:all 0.2s;flex-shrink:0;">批量下载</div>
        <div class="settings-btn" style="padding:5px 8px;background:#5654a2;color:#fff;border-radius:12px;font-size:10px;cursor:pointer;white-space:nowrap;transition:all 0.2s;flex-shrink:0;">格式设置</div>
        <div class="clear-btn" style="padding:5px 8px;background:#eb002f;color:#fff;border-radius:12px;font-size:10px;cursor:pointer;white-space:nowrap;transition:all 0.2s;flex-shrink:0;">清空嗅探</div>
        <div class="filter-btn" style="padding:5px 8px;background:#9b7c4d;color:#fff;border-radius:12px;font-size:10px;cursor:pointer;white-space:nowrap;display:flex;align-items:center;gap:3px;transition:all 0.2s;flex-shrink:0;">已过滤<span id="filterCount" style="background:rgba(0,0,0,0.3);border-radius:10px;padding:1px 4px;font-size:9px;white-space:nowrap;">0</span></div>
    </div>`;
    controlRow.querySelectorAll("div").forEach(el => {
        el.onmouseover = () => el.style.transform = "scale(1.05)";
        el.onmouseout = () => el.style.transform = "scale(1)";
    });
    container.appendChild(controlRow);

    list = document.createElement("div");
    list.id = "sniffer-list-container";
    list.style = `flex:1;overflow-y:auto;padding:4px;`;
    container.appendChild(list);

    const style = document.createElement("style");
    style.textContent = `
    .format-settings-panel {
        position: fixed;
        top: 50%; left: 50%;
        transform: translate(-50%, -50%);
        background: rgba(0,0,0,0.55);
        backdrop-filter: blur(12px);
        border: 1px solid rgba(255,255,255,0.15);
        border-radius: 14px;
        padding: 20px;
        width: 460px;
        max-width: 92vw;
        max-height: 80vh;
        overflow-y: auto;
        z-index: 99999;
    }
    .format-settings-header {
        display: flex; justify-content: space-between; align-items: center;
        margin-bottom: 15px; padding-bottom: 10px;
        border-bottom: 1px solid rgba(255,255,255,0.1);
    }
    .format-settings-title { font-size: 15px; color: #fff; font-weight: bold; }
    .format-settings-close {
        background: none; border: none; color: #ccc; font-size: 20px; cursor: pointer;
    }
    .format-settings-close:hover { color: #fff; }

    .format-settings-group { margin-bottom: 16px; }
    .format-settings-group-title {
        display: flex; justify-content: space-between; align-items: center;
        font-size: 13px; color: #40a9ff; margin-bottom: 8px;
    }
    .format-group-controls { display: flex; gap: 10px; }
    .format-group-control-btn {
        font-size: 12px; color: #ccc; cursor: pointer;
        padding: 2px 6px; border-radius: 4px;
    }
    .format-group-control-btn:hover {
        color: #fff; background: rgba(255,255,255,0.1);
    }

    .format-checkbox-container {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 8px;
        margin-top: 8px;
    }
    .format-checkbox-item {
        display: flex; align-items: center; gap: 6px;
        font-size: 12px; color: #fff; cursor: pointer;
        padding: 4px; border-radius: 4px;
    }
    .format-checkbox-item:hover { background: rgba(255,255,255,0.1); }
    .format-checkbox-item.checked { color: #40a9ff; }

    .item{
        background:rgba(255,255,255,0.05);color:#fff;padding:12px;
        border-radius:10px;margin:6px 0;font-size:12px;
        border-left:3px solid #40a9ff;transition:background 0.2s;
    }
    .item:hover{background:rgba(255,255,255,0.08);}
    .item.mp4{border-left-color:#52c41a;}.item.m3u8{border-left-color:#fa8c16;}.item.ts{border-left-color:#1890ff;}
    .item.audio{border-left-color:#722ed1;}
    .item.jpg,.item.jpeg{border-left-color:#fa541c;}.item.png{border-left-color:#13c2c2;}.item.gif{border-left-color:#eb2f96;}
    .item.bmp{border-left-color:#722ed1;}.item.webp{border-left-color:#fa8c16;}.item.svg{border-left-color:#f759ab;}
    .item.ico{border-left-color:#52c41a;}.item.tiff{border-left-color:#1890ff;}
    .item.heic,.item.avif{border-left-color:#40a9ff;}.item.jfif,.item.pjpeg{border-left-color:#fa8c16;}.item.jxr{border-left-color:#666;}
    .item.image{border-left-color:#f759ab;}.item.pdf{border-left-color:#fa541c;}.item.archive{border-left-color:#13c2c2;}.item.app{border-left-color:#eb2f96;}
    .tag{color:#ffd040;font-weight:bold;margin-bottom:6px;display:flex;justify-content:space-between;align-items:center;}
    .url{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:4px 0;cursor:pointer;color:#eee;line-height:1.4;transition:color 0.2s;}
    .url:hover{color:#fff;}
    .url.exp{white-space:normal;word-break:break-all;}
    .info{font-size:11px;color:#aaa;margin:2px 0 6px;}
    .bar{display:flex;gap:12px;margin-top:10px;flex-wrap:wrap;}
    .cp,.open-btn,.download-btn,.convert-btn{cursor:pointer;padding:3px 8px;border:1px solid;border-radius:6px;font-size:11px;}
    .cp{color:#40a9ff;border-color:#40a9ff;}.open-btn{color:#d7bde2;border-color:#d7bde2;}
    .download-btn{color:#a3e4d7;border-color:#a3e4d7;}.convert-btn{color:#ff9f8c;border-color:#ff9f8c;}
    .hide .item{display:none;}
    .format-badge{font-size:9px;padding:1px 5px;border-radius:4px;margin-left:6px;}
    .category-tab[data-category="all"]{background:#666;color:white;}
    .category-tab[data-category="video"]{background:#52c41a;color:white;}
    .category-tab[data-category="audio"]{background:#722ed1;color:white;}
    .category-tab[data-category="image"]{background:#f759ab;color:white;}
    .category-tab[data-category="document"]{background:#fa541c;color:white;}
    .category-tab[data-category="archive"]{background:#13c2c2;color:white;}
    .category-tab[data-category="app"]{background:#eb2f96;color:white;}
    .category-tab[data-category="other"]{background:#666;color:white;}
    .size-filter-section {
        margin-top: 20px; padding-top: 15px;
        border-top: 1px solid rgba(255,255,255,0.1); color:#fff;
    }
    .switch-row {
        display: flex; justify-content: space-between; align-items: center;
        margin-bottom: 10px; font-size: 12px;
    }
    .size-filter-row {
        display: flex; align-items: center; margin-bottom: 8px; font-size: 12px;
    }
    .size-filter-label { width: 80px; }
    .size-filter-input {
        flex: 1; background: rgba(255,255,255,0.08);
        border: 1px solid rgba(255,255,255,0.15);
        border-radius: 6px; padding: 5px;
        color: #fff; font-size: 12px;
    }
    .switch {
      position: relative; display: inline-block;
      width: 34px; height: 18px;
    }
    .switch input { opacity: 0; width: 0; height: 0; }
    .slider {
      position: absolute; cursor: pointer;
      top: 0; left: 0; right: 0; bottom: 0;
      background-color: #444; transition: .3s;
      border-radius: 18px;
    }
    .slider:before {
      position: absolute; content: "";
      height: 14px; width: 14px;
      left: 2px; bottom: 2px;
      background-color: white; transition: .3s;
      border-radius: 50%;
    }
    input:checked + .slider { background-color: #40a9ff; }
    input:checked + .slider:before { transform: translateX(16px); }
    `;
    document.head.appendChild(style);

    // ===== 拖拽逻辑(原样保留)=====
    let dragStartX, dragStartY, boxStartLeft, boxStartTop;
    const dragIcon = box.querySelector(".icon");
    dragIcon.addEventListener('mousedown', e => {
        dragStartX = e.clientX;
        dragStartY = e.clientY;
        boxStartLeft = box.offsetLeft;
        boxStartTop = box.offsetTop;
        document.addEventListener('mousemove', onDrag);
        document.addEventListener('mouseup', stopDrag);
    });
    function onDrag(e) {
        const dx = e.clientX - dragStartX;
        const dy = e.clientY - dragStartY;
        box.style.left = boxStartLeft + dx + 'px';
        box.style.top = boxStartTop + dy + 'px';
    }
    function stopDrag() {
        document.removeEventListener('mousemove', onDrag);
        document.removeEventListener('mouseup', stopDrag);
    }

    let tx, ty, l, t, moved = false; let isLongPress = false; // 新增:标记是否触发了长按
    dragIcon.addEventListener('touchstart', e => {
        tx = e.touches[0].clientX;
        ty = e.touches[0].clientY;
        l = box.offsetLeft;
        t = box.offsetTop;
        moved = false;
    });
    dragIcon.addEventListener('touchmove', e => {
        e.stopPropagation();
        let dx = e.touches[0].clientX - tx;
        let dy = e.touches[0].clientY - ty;
        if (Math.hypot(dx, dy) > 6) moved = true;
        box.style.left = l + dx + 'px';
        box.style.top = t + dy + 'px';
    });
    dragIcon.addEventListener('touchend', () => {
// 触发了长按 或 拖动,都不执行面板切换
if (isLongPress || moved) {
    isLongPress = false; // 重置标记
    return;
}
container.style.display = container.style.display === 'flex' ? 'none' : 'flex'; });

    // 新增:长按悬浮球 隐藏/关闭悬浮球 let longPressTimer = null; const LONG_TIME = 600; // 长按600毫秒触发

dragIcon.addEventListener(‘mousedown’, () => { longPressTimer = setTimeout(() => { isLongPress = true; // 标记长按触发 const icon = document.querySelector(‘.icon’); if(icon) icon.style.display = ‘none’; msg(‘悬浮球已隐藏,刷新页面恢复’);

}, LONG_TIME); });

dragIcon.addEventListener(‘mouseup’, () => { if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = null; } });

dragIcon.addEventListener(‘mouseleave’, () => { if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = null; } });

// 移动端触摸长按兼容 dragIcon.addEventListener(‘touchstart’, () => { longPressTimer = setTimeout(() => { isLongPress = true; // 标记长按触发 const icon = document.querySelector(‘.icon’); if(icon) icon.style.display = ‘none’; msg(‘悬浮球已隐藏,刷新页面恢复’);

}, LONG_TIME); });

dragIcon.addEventListener(‘touchend’, () => { if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = null; } });

dragIcon.addEventListener(‘touchcancel’, () => { if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = null; } });

    // ===== 按钮事件 =====
    controlRow.querySelector('.clear-btn').onclick = () => {
        allItems = [];
        shownUrls.clear();
        list.innerHTML = '';
        sn = 1;
        invalidLinkCount = 0;
        filteredHideList = [];
        filteredUrlSet.clear();
        resetFormatCounts();
        updateBadge();
        updateFormatCountDisplay();
        document.getElementById('invalidBadge').innerText = "0";
        document.getElementById('filterCount').innerText = "0";
        msg("✅ 已清空所有嗅探资源");
    };

    controlRow.querySelector('.filter-btn').onclick = showHideFilterPanel;

    controlRow.querySelector('.batch-btn').onclick = () => {
        const videoList = allItems.filter(item => getFileCategory(item.type) === 'video');
        const audioList = allItems.filter(item => getFileCategory(item.type) === 'audio');
        const imageList = allItems.filter(item => getFileCategory(item.type) === 'image');
        const allList = [...allItems];

        let mask = document.createElement('div');
        mask.style = `position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:99999998;display:flex;align-items:center;justify-content:center;`;
        let b = document.createElement('div');
        b.style = `background:#222;padding:20px;border-radius:12px;border:1px solid rgba(255,255,255,0.15);`;
        b.innerHTML = `
<div style="color:#fff !important;margin-bottom:15px !important;text-align:center !important;font-size:14px !important;">选择批量下载分类</div>
<div style="display:flex !important;flex-direction:column !important;gap:10px !important;">
    <button class="down-btn" data-type="video" style="padding:8px 20px !important;border:none !important;border-radius:6px !important;background:#40a9ff !important;color:#fff !important;font-size:13px !important;">视频 (${videoList.length})</button>
    <button class="down-btn" data-type="audio" style="padding:8px 20px !important;border:none !important;border-radius:6px !important;background:#52c41a !important;color:#fff !important;font-size:13px !important;">音频 (${audioList.length})</button>
    <button class="down-btn" data-type="image" style="padding:8px 20px !important;border:none !important;border-radius:6px !important;background:#faad14 !important;color:#fff !important;font-size:13px !important;">图片 (${imageList.length})</button>
    <button class="down-btn" data-type="all" style="padding:8px 20px !important;border:none !important;border-radius:6px !important;background:#722ed1 !important;color:#fff !important;font-size:13px !important;">全部 (${allList.length})</button>
    <button class="close-btn" style="padding:8px 20px !important;border:none !important;border-radius:6px !important;background:#444 !important;color:#ccc !important;font-size:13px !important;">取消</button>
</div>`;

        mask.appendChild(b);
        document.body.appendChild(mask);

        const closePop = () => mask.remove();

        b.querySelectorAll('.down-btn').forEach(btn => {
            btn.onclick = () => {
                let type = btn.dataset.type;
                let arr = [];
                if (type === 'video') arr = videoList;
                else if (type === 'audio') arr = audioList;
                else if (type === 'image') arr = imageList;
                else arr = allList;

                if (arr.length === 0) {
                    msg("该分类暂无资源");
                    closePop();
                    return;
                }

                arr.filter(i => !isInvalidItem(i)).forEach((item, i) => {
                    setTimeout(() => {
                        mgmapi.downloadFile({
                            url: item.realUrl,
                            name: `file_${i + 1}`
                        });
                    }, i * 400);
                });

                msg(`已开始批量下载 ${arr.length} 个文件`);
                closePop();
            };
        });

        b.querySelector('.close-btn').onclick = closePop;
        mask.onclick = e => e.target === mask && closePop();
    };

    controlRow.querySelector('.settings-btn').onclick = () => {
        toggleFormatSettingsPanel();
    };
}

function initCategoryTabs(wrap) {
const cats = [
    { key: 'all', name: '全部', bg: '#9a8c98' },
    { key: 'video', name: '视频', bg: '#52c41a' },
    { key: 'audio', name: '音频', bg: '#40a9ff' },
    { key: 'image', name: '图片', bg: '#f759ab' },
    { key: 'document', name: '文档', bg: '#fa541c' },
    { key: 'archive', name: '压缩', bg: '#13c2c2' },
    { key: 'app', name: '应用', bg: '#ff9a9e' },
    { key: 'other', name: '其他', bg: '#cdedf6' }
];

wrap.style.display = 'flex';
wrap.style.flexWrap = 'wrap';
wrap.style.gap = '4px';
wrap.style.rowGap = '6px';
wrap.style.justifyContent = 'flex-start';
wrap.style.alignItems = 'center';
wrap.style.padding = '6px 8px';
wrap.style.boxSizing = 'border-box';

cats.forEach(c => {
    const tab = document.createElement('div');
    tab.className = 'category-tab';
    tab.style.padding = '4px 7px';
    tab.style.color = '#fff !important';
    tab.style.borderRadius = '10px';
    tab.style.fontSize = '10px !important';
    tab.style.cursor = 'pointer';
    tab.style.whiteSpace = 'nowrap';
    tab.style.transition = 'all 0.2s';
    tab.style.boxSizing = 'border-box';
    tab.style.background = c.bg;
    tab.style.flex = '0 0 auto';
    tab.dataset.category = c.key;
    tab.innerHTML = `${c.name}<span class="category-count">0</span>`;
    tab.onclick = () => switchCategory(c.key);
    wrap.appendChild(tab);
});

const clearRoundBtn = document.createElement('div');
clearRoundBtn.classList.add('clear-animate-btn');
clearRoundBtn.style.width = '20px';
clearRoundBtn.style.height = '20px';
clearRoundBtn.style.borderRadius = '50%';
clearRoundBtn.style.background = '#b56576';
clearRoundBtn.style.display = 'flex';
clearRoundBtn.style.alignItems = 'center';
clearRoundBtn.style.justifyContent = 'center';
clearRoundBtn.style.cursor = 'pointer';
clearRoundBtn.style.position = 'relative';
clearRoundBtn.style.transition = 'all 0.2s';
clearRoundBtn.style.marginLeft = 'auto';
clearRoundBtn.style.marginRight = '8px';
clearRoundBtn.innerText = '清';
clearRoundBtn.style.color = '#fff !important';
clearRoundBtn.style.fontSize = '8px !important';
clearRoundBtn.style.boxShadow = '0 0 8px 3px hsla(0,100%,60%,0.5)';
clearRoundBtn.style.animation = 'tabAllRainbow 3s ease-in-out infinite';
clearRoundBtn.style.transformOrigin = 'center center';

clearRoundBtn.innerHTML += `<span id="invalidBadge" style="position:absolute;right:-2px;top:-2px;background:#000;color:#fff !important;font-size:6px !important;padding:0;border-radius:50%;width:10px;height:10px;line-height:10px;text-align:center;border:1px solid #fff;opacity:0.5;">0</span>`;

clearRoundBtn.onmouseover = () => clearRoundBtn.style.transform = 'scale(1.08)';
clearRoundBtn.onmouseout = () => clearRoundBtn.style.transform = 'scale(1)';
clearRoundBtn.onclick = function () {
    allItems = allItems.filter(item => !isInvalidItem(item));
    invalidLinkCount = 0;
    sn = 1;
    list.innerHTML = "";
    allItems.forEach(renderItem);
    updateBadge();
    updateFormatCountDisplay();
    document.getElementById('invalidBadge').innerText = "0";
    msg("✅ 已清理无效链接");
    setTimeout(() => updateBadge(), 20);
};
wrap.appendChild(clearRoundBtn); }


function switchCategory(cat) {
    currentFilter = cat;
    sn = 1;
    list.innerHTML = "";

    document.querySelectorAll('.category-tab').forEach(t => {
        t.classList.remove('active');
        if (t.dataset.category === cat) t.classList.add('active');
    });

    renderFilterList();
}

function resetFormatCounts() {
    Object.keys(formatCounts).forEach(k => formatCounts[k] = 0);
}

function updateBadge() {
    const total = allItems.length;
    const filter = filteredHideList.length;
    const badge = box.querySelector(".tipBadge");
    badge.innerText = total + "|" + filter;

    const icon = box.querySelector(".icon");
    icon.classList.toggle('active', total > 0);

    let btn = document.querySelector('.clear-animate-btn');
    if (!btn) return;

    let badgeDom = document.getElementById('invalidBadge');
    let num = badgeDom ? parseInt(badgeDom.innerText) || 0 : 0;

    if (num > 0) {
        btn.classList.add('run-ani');
    } else {
        btn.classList.remove('run-ani');
    }
}

function updateFormatCountDisplay() {
    let videoNum = formatCounts.MP4 + formatCounts.M3U8 + formatCounts.TS + formatCounts.FLV + formatCounts.MKV + formatCounts.WEBM + formatCounts.AVI + formatCounts.MOV + formatCounts.WMV + formatCounts.VIDEO;
    let audioNum = formatCounts.AAC + formatCounts.MP3 + formatCounts.AUDIO;
    let imgNum = formatCounts.JPG + formatCounts.PNG + formatCounts.GIF + formatCounts.BMP + formatCounts.WEBP + formatCounts.SVG + formatCounts.ICO + formatCounts.TIFF + formatCounts.HEIC + formatCounts.AVIF + formatCounts.JFIF + formatCounts.PJPEG + formatCounts.JXR + formatCounts.IMAGE;
    let otherNum = formatCounts.PDF + formatCounts.ZIP + formatCounts.RAR + formatCounts.APK + formatCounts.EXE + formatCounts.OTHER;

    if (allItems.length > 0) {
        let v = allItems.filter(x => getFileCategory(x.type) === 'video').length;
        let a = allItems.filter(x => getFileCategory(x.type) === 'audio').length;
        let i = allItems.filter(x => getFileCategory(x.type) === 'image').length;
        let o = allItems.filter(x => ['other', 'document', 'archive', 'app'].includes(getFileCategory(x.type))).length;

        formatCountDisplay.innerHTML = `
<div style="font-size:9px !important;">
    <span style="color:#4ade80;font-size:inherit !important;">视${v}</span>
    <span style="color:#60a5fa;font-size:inherit !important;"> 音${a}</span>
    <span style="color:#f472b6;font-size:inherit !important;"> 图${i}</span>
    <span style="color:#cdedf6;font-size:inherit !important;"> 其${o}</span>
</div>`; } else {
formatCountDisplay.innerHTML = `<div>资源嗅探</div><div style="font-size:8px !important;color:#aaa !important;">点击展开</div>`; }


    document.querySelectorAll('.category-tab').forEach(tab => {
        const key = tab.dataset.category;
        let num = 0;
        if (key === 'all') num = allItems.length;
        else num = allItems.filter(item => getFileCategory(item.type) === key).length;
        tab.querySelector('.category-count').innerText = num;
    });

    document.getElementById('filterCount').innerText = filteredHideList.length;
    document.getElementById('invalidBadge').innerText = invalidLinkCount;
}

function renderFilterList() {
    if (window._renderFilterTimer) clearTimeout(window._renderFilterTimer);

    window._renderFilterTimer = setTimeout(() => {
        list.innerHTML = '';
        const filterArr = currentFilter === 'all'
            ? allItems
            : allItems.filter(item => getFileCategory(item.type) === currentFilter);

        let index = 1;
        filterArr.forEach(item => renderItem(item, index++));
    }, 120);
}

function renderItem(item, index) {
    const div = document.createElement('div');
    div.className = `item ${item.type.toLowerCase()}`;

    let itemBg = "";
    switch (item.type) {
        case 'MP4': itemBg = 'rgba(39, 174, 96, 0.15)'; break;
        case 'M3U8': itemBg = 'rgba(230, 126, 34, 0.15)'; break;
        case 'TS': itemBg = 'rgba(52, 152, 219, 0.15)'; break;
        case 'FLV': itemBg = 'rgba(155, 89, 182, 0.15)'; break;
        case 'MKV': itemBg = 'rgba(22, 160, 133, 0.15)'; break;
        case 'WEBM': itemBg = 'rgba(41, 128, 185, 0.15)'; break;
        case 'AVI': itemBg = 'rgba(192, 57, 43, 0.15)'; break;
        case 'MOV': itemBg = 'rgba(142, 68, 173, 0.15)'; break;
        case 'WMV': itemBg = 'rgba(127, 140, 141, 0.15)'; break;
        case 'MP3': itemBg = 'rgba(135, 206, 250, 0.15)'; break;
        case 'AAC': itemBg = 'rgba(9, 132, 227, 0.15)'; break;
        case 'JPG': itemBg = 'rgba(253, 121, 168, 0.15)'; break;
        case 'PNG': itemBg = 'rgba(0, 206, 201, 0.15)'; break;
        case 'GIF': itemBg = 'rgba(108, 92, 231, 0.15)'; break;
        case 'WEBP': itemBg = 'rgba(253, 203, 110, 0.15)'; break;
        case 'PDF': itemBg = 'rgba(214, 48, 49, 0.15)'; break;
        case 'ZIP': itemBg = 'rgba(0, 184, 148, 0.15)'; break;
        case 'RAR': itemBg = 'rgba(99, 110, 114, 0.15)'; break;
        case 'APK': itemBg = 'rgba(225, 112, 85, 0.15)'; break;
        default: itemBg = 'rgba(99, 110, 114, 0.15)';
    }

    div.style.cssText = `margin:7px 0;padding:10px;border-radius:8px;background:${itemBg};`;

    div.innerHTML = `
    <div class="tag" style="display:flex;justify-content:space-between;align-items:center;">
        <span style="color:#ffd700">${index}.
            <span style="padding:2px 6px;border-radius:4px;font-size:10px;color:#fff;margin-left:4px;
            ${item.type === 'MP4' ? 'background:#27ae60;' :
            item.type === 'M3U8' ? 'background:#e67e22;' :
            item.type === 'TS' ? 'background:#3498db;' :
            item.type === 'FLV' ? 'background:#9b59b6;' :
            item.type === 'MKV' ? 'background:#16a085;' :
            item.type === 'WEBM' ? 'background:#2980b9;' :
            item.type === 'AVI' ? 'background:#c0392b;' :
            item.type === 'MOV' ? 'background:#8e44ad;' :
            item.type === 'WMV' ? 'background:#7f8c8d;' :
            item.type === 'MP3' ? 'background:#87cefa;' :
            item.type === 'AAC' ? 'background:#0984e3;' :
            item.type === 'JPG' ? 'background:#fd79a8;' :
            item.type === 'PNG' ? 'background:#00cec9;' :
            item.type === 'GIF' ? 'background:#6c5ce7;' :
            item.type === 'WEBP' ? 'background:#fdcb6e;' :
            item.type === 'PDF' ? 'background:#d63031;' :
            item.type === 'ZIP' ? 'background:#00b894;' :
            item.type === 'RAR' ? 'background:#636e72;' :
            item.type === 'APK' ? 'background:#e17055;' : 'background:#636e72;'}">${item.type}</span>
        </span>
        <span style="color:#aaa;font-size:9px">${item.info || '正常资源'}</span>
    </div>
    <div class="url" title="${item.url}">${item.url}</div>
    <div class="bar" style="display:flex;gap:8px;margin-top:6px;">
        <span class="cp" data-url="${item.url}" style="color:#40a9ff;border:1px solid #40a9ff;border-radius:4px;padding:2px 8px;cursor:pointer;font-size:12px;">复制</span>
        <span class="open-btn" data-url="${item.url}" style="color:#52c41a;border:1px solid #52c41a;border-radius:4px;padding:2px 8px;cursor:pointer;font-size:12px;">新标签打开</span>
        <span class="download-btn" data-url="${item.url}" data-name="${item.url.split('/').pop()}" style="color:#fa8c16;border:1px solid #fa8c16;border-radius:4px;padding:2px 8px;cursor:pointer;font-size:12px;">下载</span>
        <span class="convert-btn" data-url="${item.url}" style="color:#c77dff;border:1px solid #c77dff;border-radius:4px;padding:2px 8px;cursor:pointer;font-size:12px;">转换</span>
    </div>
    `;

    list.appendChild(div);

    div.querySelector('.cp').onclick = () => mgmapi.copyText(item.url);
    div.querySelector('.open-btn').onclick = () => mgmapi.openInTab(item.url);
    div.querySelector('.download-btn').onclick = function () {
        mgmapi.downloadFile({ url: this.dataset.url, name: this.dataset.name });
    };
    div.querySelector('.convert-btn').onclick = () => handleConversion(item.url);
    div.querySelector('.url').onclick = function () {
        this.classList.toggle('exp');
    };
}

async function addItem({ type, url }) {
    if (!url) return;
    const fixUrlStr = fixUrl(url);
    if (shownUrls.has(fixUrlStr)) return;
    if (filteredUrlSet.has(fixUrlStr)) return;

    if (!enabledFormats[type]) {
        filteredHideList.push({ type, url: fixUrlStr });
        filteredUrlSet.add(fixUrlStr);
        filterCountNum++;
        updateFormatCountDisplay();
        updateBadge();
        return;
    }

    shownUrls.add(fixUrlStr);
    formatCounts[type]++;

    const resInfo = await getInfoByUrl(fixUrlStr, type);
    const itemData = {
        type,
        url: fixUrlStr,
        info: resInfo.info,
        size: resInfo.size,
        realUrl: resInfo.realUrl
    };

    if (!checkSizeFilter(resInfo.size)) {
        filteredHideList.push(itemData);
        filteredUrlSet.add(fixUrlStr);
        filterCountNum++;
        updateFormatCountDisplay();
        updateBadge();
        return;
    }

    if (isInvalidItem(itemData)) {
        invalidLinkCount++;
        updateFormatCountDisplay();
    }

    allItems.push(itemData);
    playRingAnim(url);

    renderFilterList();
    updateBadge();
    updateFormatCountDisplay();
}

function toggleFormatSettingsPanel() {
    isFormatSettingsOpen = !isFormatSettingsOpen;
    if (isFormatSettingsOpen) {
        createFormatSettingsPanel();
    } else {
        const p = document.querySelector('.format-settings-panel');
        if (p) p.remove();
    }
}

function createFormatSettingsPanel() {
    let old = document.querySelector('.format-settings-panel');
    if (old) old.remove();

    const panel = document.createElement('div');
    panel.className = 'format-settings-panel'; panel.style.zIndex = '2147483647';
    panel.innerHTML = `
    <div class="format-settings-header" style="!important;">
        <span class="format-settings-title" style="!important;">格式过滤设置</span>
        <button class="format-settings-close" style="!important;">×</button>
    </div>

    <div style="display:flex;gap:10px;margin-bottom:12px;padding-bottom:10px;border-bottom:1px solid rgba(255,255,255,0.1);!important;">
        <span class="format-group-control-btn" id="allCheck" style="!important;">全部全选</span>
        <span class="format-group-control-btn" id="allUncheck" style="!important;">全部取消</span>
    </div>

    <div class="format-settings-group" style="!important;">
        <div class="format-settings-group-title" style="!important;">
            视频格式
            <div class="format-group-controls">
                <span class="format-group-control-btn check-all-video" style="!important;">全选</span>
                <span class="format-group-control-btn uncheck-all-video" style="!important;">取消</span>
            </div>
        </div>
        <div class="format-checkbox-container" id="video-format-group" style="!important;"></div>
    </div>

    <div class="format-settings-group" style="!important;">
        <div class="format-settings-group-title" style="!important;">
            音频格式
            <div class="format-group-controls">
                <span class="format-group-control-btn check-all-audio" style="!important;">全选</span>
                <span class="format-group-control-btn uncheck-all-audio" style="!important;">取消</span>
            </div>
        </div>
        <div class="format-checkbox-container" id="audio-format-group" style="!important;"></div>
    </div>

    <div class="format-settings-group" style="!important;">
        <div class="format-settings-group-title" style="!important;">
            图片格式
            <div class="format-group-controls">
                <span class="format-group-control-btn check-all-image" style="!important;">全选</span>
                <span class="format-group-control-btn uncheck-all-image" style="!important;">取消</span>
            </div>
        </div>
        <div class="format-checkbox-container" id="image-format-group" style="!important;"></div>
    </div>

    <div class="format-settings-group" style="!important;">
        <div class="format-settings-group-title" style="!important;">
            其他格式
            <div class="format-group-controls">
                <span class="format-group-control-btn check-all-other" style="!important;">全选</span>
                <span class="format-group-control-btn uncheck-all-other" style="!important;">取消</span>
            </div>
        </div>
        <div class="format-checkbox-container" id="other-format-group" style="!important;"></div>
    </div>

    <div class="size-filter-section" style="!important;">
        <div class="switch-row" style="!important;">
            <span class="switch-label" style="!important;">启用大小过滤</span>
            <label class="switch">
                <input type="checkbox" id="sizeFilterEnable" ${sizeFilterSettings.enabled ? 'checked' : ''}>
                <span class="slider"></span>
            </label>
        </div>
        <div class="size-filter-row" style="!important;">
            <span class="size-filter-label" style="!important;">最小KB:</span>
            <input type="number" class="size-filter-input" id="minSizeInput" value="${sizeFilterSettings.minSize}" style="!important;">
        </div>
        <div class="size-filter-row" style="!important;">
            <span class="size-filter-label" style="!important;">最大KB:</span>
            <input type="number" class="size-filter-input" id="maxSizeInput" value="${sizeFilterSettings.maxSize}" style="!important;">
        </div>
        <div style="text-align:center;margin-top:15px;!important;">
            <span class="format-group-control-btn" id="saveSizeBtn" style="!important;">保存设置</span>
        </div>
    </div>
    `;


    document.body.appendChild(panel);

    panel.querySelector('.format-settings-close').onclick = () => {
        document.querySelectorAll('.format-checkbox-container input[type="checkbox"]').forEach(ck => {
            const type = ck.id.replace('fmt_', '');
            enabledFormats[type] = ck.checked;
        });
        mgmapi.setValue('formatEnabledFormats', { ...enabledFormats });

        const sizeEnable = document.querySelector('#sizeFilterEnable');
        const minInput = document.querySelector('#minSizeInput');
        const maxInput = document.querySelector('#maxSizeInput');

        if (sizeEnable && minInput && maxInput) {
            sizeFilterSettings.enabled = sizeEnable.checked;
            sizeFilterSettings.minSize = parseInt(minInput.value) || 0;
            sizeFilterSettings.maxSize = parseInt(maxInput.value) || 0;

            mgmapi.setValue('sizeFilterEnable', sizeFilterSettings.enabled);
            mgmapi.setValue('sizeFilterMin', sizeFilterSettings.minSize);
            mgmapi.setValue('sizeFilterMax', sizeFilterSettings.maxSize);
        }

        panel.remove();
        isFormatSettingsOpen = false;
    };

    fillFormatGroup('video-format-group', ['MP4', 'M3U8', 'TS', 'FLV', 'MKV', 'WEBM', 'AVI', 'MOV', 'WMV', 'VIDEO']);
    fillFormatGroup('audio-format-group', ['AAC', 'MP3', 'AUDIO']);
    fillFormatGroup('image-format-group', ['JPG', 'PNG', 'GIF', 'BMP', 'WEBP', 'SVG', 'ICO', 'TIFF', 'HEIC', 'AVIF', 'JFIF', 'PJPEG', 'JXR', 'IMAGE']);
    fillFormatGroup('other-format-group', ['PDF', 'ZIP', 'RAR', 'APK', 'EXE', 'OTHER']);

    bindGroupCheck('check-all-video', 'video-format-group', true);
    bindGroupCheck('uncheck-all-video', 'video-format-group', false);
    bindGroupCheck('check-all-audio', 'audio-format-group', true);
    bindGroupCheck('uncheck-all-audio', 'audio-format-group', false);
    bindGroupCheck('check-all-image', 'image-format-group', true);
    bindGroupCheck('uncheck-all-image', 'image-format-group', false);
    bindGroupCheck('check-all-other', 'other-format-group', true);
    bindGroupCheck('uncheck-all-other', 'other-format-group', false);

    document.getElementById('allCheck').onclick = () => {
        document.querySelectorAll('.format-checkbox-item').forEach(item => {
            const type = item.querySelector('.name').textContent;
            enabledFormats[type] = true;
            item.classList.add('checked');
            item.querySelector('.mark').textContent = '√';
        });
        mgmapi.setValue('formatEnabledFormats', { ...enabledFormats });
        msg("✅ 全部格式已全选并保存");
    };

    document.getElementById('allUncheck').onclick = () => {
        document.querySelectorAll('.format-checkbox-item').forEach(item => {
            const type = item.querySelector('.name').textContent;
            enabledFormats[type] = false;
            item.classList.remove('checked');
            item.querySelector('.mark').textContent = '□';
        });
        mgmapi.setValue('formatEnabledFormats', { ...enabledFormats });
        msg("✅ 全部格式已取消并保存");
    };

    document.getElementById('saveSizeBtn').onclick = function () {
        const sizeEnable = document.querySelector('#sizeFilterEnable');
        const minInput = document.querySelector('#minSizeInput');
        const maxInput = document.querySelector('#maxSizeInput');
        sizeFilterSettings.enabled = sizeEnable.checked;
        sizeFilterSettings.minSize = parseInt(minInput.value) || 0;
        sizeFilterSettings.maxSize = parseInt(maxInput.value) || 0;

        mgmapi.setValue('sizeFilterEnable', sizeFilterSettings.enabled);
        mgmapi.setValue('sizeFilterMin', sizeFilterSettings.minSize);
        mgmapi.setValue('sizeFilterMax', sizeFilterSettings.maxSize);

        msg("✅ 大小过滤设置已保存");
        panel.remove();
        isFormatSettingsOpen = false;
    };
}

function fillFormatGroup(wrapId, typeList) {
    const wrap = document.getElementById(wrapId);
    typeList.forEach(type => {
        if (enabledFormats[type] === undefined) {
            enabledFormats[type] = true;
        }

        const item = document.createElement('div');
        item.className = 'format-checkbox-item ' + (enabledFormats[type] ? 'checked' : '');
        item.innerHTML = `
<span class="mark" style="font-size:inherit !important;">${enabledFormats[type] ? '√' : '□'}</span>
<span class="name" style="font-size:inherit !important;">${type}</span> `; wrap.appendChild(item);

item.onclick = () => { enabledFormats[type] = !enabledFormats[type]; item.classList.toggle(‘checked’, enabledFormats[type]); item.querySelector(‘.mark’).textContent = enabledFormats[type] ? ‘√’ : ‘□’; mgmapi.setValue(‘formatEnabledFormats’, { …enabledFormats }); }; }); }

function bindGroupCheck(btnClass, wrapId, checkState) { const btn = document.querySelector(.${btnClass}); btn.onclick = () => { const wrap = document.getElementById(wrapId); wrap.querySelectorAll(‘.format-checkbox-item’).forEach(item => { const type = item.querySelector(‘.name’).textContent; enabledFormats[type] = checkState; item.classList.toggle(‘checked’, checkState); item.querySelector(‘.mark’).textContent = checkState ? ‘√’ : ‘□’; }); mgmapi.setValue(‘formatEnabledFormats’, { …enabledFormats }); msg(checkState ? “✅ 已全选” : “✅ 已取消全选”); }; }

function playRingAnim(url) {
const ballIcon = document.querySelector('.icon');
if (!ballIcon) return;

if (ballIcon._starTimer) clearTimeout(ballIcon._starTimer);

let oldStarWrap = ballIcon.querySelector('.star-orbit-wrap');
if (oldStarWrap) oldStarWrap.remove();

let starWrap = document.createElement('div');
starWrap.className = "star-orbit-wrap circular-ring";
starWrap.style.animation = "starRotate 7s linear infinite";
// 不修改父球位置,只加外圈特效
ballIcon.appendChild(starWrap);

let starType = "video";
let lowUrl = url.toLowerCase();
if (lowUrl.includes('.mp3') || lowUrl.includes('.aac')) starType = "audio";
else if (lowUrl.includes('.jpg') || lowUrl.includes('.png') || lowUrl.includes('.gif')) starType = "image";
else if (lowUrl.includes('.pdf')) starType = "doc";
else if (lowUrl.includes('.zip') || lowUrl.includes('.rar')) starType = "zip";

let starText = ['★', '✦', '✧', '☆', '✩', '✪', '✫', '✬'];
for (let i = 0; i < 8; i++) {
    let star = document.createElement('span');
    star.className = `star-item ${starType}`;
    star.innerText = starText[i];
    star.style.position = "absolute";
    star.style.left = "50%";
    star.top = "0";
    star.style.fontSize = "11px";
    star.style.transformOrigin = "center 26px";
    star.style.transform = `rotate(${i * 45}deg)`;
    star.style.animation = `starTwinkle 1.6s ease infinite ${i * 0.12}s`;
    starWrap.appendChild(star);
}

ballIcon._starTimer = setTimeout(() => {
    if (starWrap) starWrap.remove();
}, 1500); } //手机滑动隐藏 互不冲突版 let scrollTimer; document.addEventListener('touchmove', ()=>{
const ic = document.querySelector('.icon');
ic.style.transform = "translateY(-50%) translateX(45px) scale(0.5)";
ic.style.opacity = 0.14;
clearTimeout(scrollTimer); }); document.addEventListener('touchend', ()=>{
scrollTimer = setTimeout(()=>{
    const ic = document.querySelector('.icon');
    ic.style.transform = "translateY(-50%)";
    ic.style.opacity = 0.8;
}, 1000); });

async function startInit() {
    try {
        await loadUserSettings();
        createHideFilterPanel();
        initUI();
        initDetect();
    } catch (e) {
        console.error('[Sniffer Init Error]', e);
    }
}

startInit();

})();


如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

¥ 打赏博主

类似帖子

上一篇 安装termux

下一篇 替换脚本

发布评论

smoothScroll.init({ speed: 500, // Integer. How fast to complete the scroll in milliseconds easing: 'easeInOutCubic', // Easing pattern to use offset: 20, // Integer. How far to offset the scrolling anchor location in pixels });