All-in-One Web Scanner [BETA]

Combines scanners, persists state, stays on top, reliably appears in SPAs, with enhanced stream detection.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

Bạn sẽ cần cài đặt một tiện ích mở rộng như Tampermonkey hoặc Violentmonkey để cài đặt kịch bản này.

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

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

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

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         All-in-One Web Scanner [BETA]
// @namespace    aio-ws-drk-beta
// @icon         https://darkie-matrix.vercel.app/scanner.png
// @supportURL   https://darkie.vercel.app/
// @version      1.7.1
// @description  Combines scanners, persists state, stays on top, reliably appears in SPAs, with enhanced stream detection.
// @author       DARKIE
// @match        *://*/*
// @exclude      *://*.youtube.com/*
// @grant        none
// @license      MIT
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    if (window.aioScannerInstanceShort) {
        console.log("AIO Scanner (Short): Initialization skipped (instance already exists).");
        return;
    }

    class AioScanner {
        // --- Constants ---
        _SK = 'aioScannerPopupState_v1';
        _Z_POP = 2147483646;
        _Z_TGL = 2147483645;
        _P_ID = 'aio-scanner-popup';
        _T_ID = 'aio-toggle-button';
        _PR_ID = 'aio-image-preview-popup';

        _STRM_MIME = {
            'application/vnd.apple.mpegurl': 'm3u8',
            'application/x-mpegurl': 'm3u8',
            'audio/mpegurl': 'm3u8',
            'video/mp4': 'mp4',
            'audio/mp4': 'mp4',
            'text/vtt': 'vtt',
            'application/x-subrip': 'srt',
            'text/plain': 'srt',
            'application/dash+xml': 'mpd',
        };
        _RGX_STRM = /["'](https?:\/\/[^"'\s]+?\.(m3u8|mp4|vtt|srt|mpd)(?:[?#][^"'\s]*)?)["']|["'](https?:\/\/[^"'\s]+?(?:hls|dash|manifest|playlist|\/video\/|\/audio\/|\/segment)[^"'\s]*?)["']/gi;

        constructor() {
            console.log("AIO Scanner (Short): Constructor called.");

            // --- Instance Variables ---
            this._urls = new Set();
            this._reqs = [];
            this._maxR = 100;
            this._lastImgScn = 0;
            this._imgScnDelay = 500;
            this._maxUrlLen = 70;
            this._prvPad = 15;
            this._prvMaxW = 300;
            this._initd = false;
            this._obs = null;
            this._lastScanTime = 0;

            // --- Configuration ---
            this._cfg = {
                ui: {
                    popup: {
                        width: '650px',
                        maxHeight: '85vh'
                    }
                },
                selectors: {
                    vtt: '#vtt-content',
                    srt: '#srt-content',
                    m3u8: '#m3u8-content',
                    mp4: '#mp4-content',
                    mpd: "#mpd-content",
                    iframe: '#iframe-content',
                    network: '#network-content',
                    image: '#image-content'
                },
                linkTypes: ['vtt', 'srt', 'm3u8', 'mp4', 'mpd']
            };

            window.allInOneScanner = this;

            // --- Debounced Functions ---
            this._debScanAll = this._dbnc(() => {
                if (this._pop && this._pop.classList.contains('aio-visible')) {
                    this._scanAll();
                }
            }, this._imgScnDelay);

            this._debEnsureUI = this._dbnc(this._ensureUI, 300);

            // --- Core Initialization Steps ---
            this._mkCSS();
            this._setupNetMon();
            this._monSetAttr();
            this._listenSPA();
            this._monBlob();

            // --- Deferred UI/Observer Setup ---
            this._waitBodyInit();
        }

        /* ==========================================================================
           Initialization Helpers
           ========================================================================== */

        _waitBodyInit() {
            if (document.body) {
                this._initUIObs();
            } else {
                const obs = new MutationObserver((muts, observer) => {
                    if (document.body) {
                        observer.disconnect();
                        this._initUIObs();
                    }
                });
                obs.observe(document.documentElement || document, {
                    childList: true
                });
            }
        }

        _initUIObs() {
            if (this._initd) return;
            this._mkUI();
            this._obsDOM();
            this._chkStUI();
            if (this._pop ?.classList.contains('aio-visible')) {
                this._scanAll();
            }
            this._initd = true;
            console.log("AIO Scanner (Short): UI Initialized and DOM Observer attached.");
        }

        /* ==========================================================================
           UI Creation and Management
           ========================================================================== */

        _mkUI() {
            this._mkTglBtn();
            this._mkPop();
            this._mkPrvPop();
            const cached = this._cacheDOM();
            if (cached) {
                this._addEvts();
                this._setupKB();
                this._setupDelEvts();
            } else {
                console.error("AIO Scanner (Short): Failed UI init - core elements missing.");
            }
        }

        _mkCSS() {
            const ZP = this._Z_POP;
            const ZT = this._Z_TGL;
            const ZPR = ZP + 1;
            const css = `
                .aio-popup {
                    position: fixed; top: 20px; right: 20px; width: ${this._cfg.ui.popup.width}; max-height: ${this._cfg.ui.popup.maxHeight};
                    background-color: rgba(35, 35, 42, 0.97); color: #e0e0e0; padding: 18px; border-radius: 14px;
                    border: 1px solid rgba(255, 255, 255, 0.1); box-shadow: 0 6px 25px rgba(0, 0, 0, 0.4);
                    font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
                    font-size: 14px; z-index: ${ZP}; display: none; overflow-y: auto; backdrop-filter: blur(8px);
                    transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out; transform: translateY(-10px); opacity: 0;
                }
                .aio-popup.aio-visible {
                    transform: translateY(0); opacity: 1; display: block;
                }
                #aio-toggle-button {
                    position: fixed; top: 20px; right: 20px; z-index: ${ZT}; padding: 8px 15px;
                    background-color: rgba(35, 35, 42, 0.95); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
                    border: 1px solid rgba(255, 255, 255, 0.1); display: block; background-color: rgba(255, 255, 255, 0.1);
                    border: 1px solid rgba(255, 255, 255, 0.15); color: #e0e0e0; border-radius: 8px; cursor: pointer;
                    font-size: 12px; transition: background-color 0.2s ease, border-color 0.2s ease, transform 0.1s ease; white-space: nowrap;
                }
                #aio-toggle-button:hover {
                    background-color: rgba(255, 255, 255, 0.18); border-color: rgba(255, 255, 255, 0.25);
                }
                #aio-toggle-button:active {
                    transform: scale(0.97);
                }
                 .aio-image-preview {
                    position: fixed; padding: 10px; background-color: rgba(35, 35, 42, 0.98); border: 1px solid rgba(255, 255, 255, 0.15);
                    border-radius: 8px; box-shadow: 0 5px 20px rgba(0, 0, 0, 0.5); z-index: ${ZPR}; pointer-events: none;
                    opacity: 0; transition: opacity 0.15s ease-in-out; max-width: ${this._prvMaxW}px; max-height: 300px; display: none;
                 }
                 .aio-image-preview img {
                     display: block; max-width: 100%; max-height: 280px; border-radius: 4px;
                 }
                .aio-header {
                    display: flex; justify-content: space-between; align-items: center; margin-bottom: 18px; padding-bottom: 10px;
                    border-bottom: 1px solid rgba(255, 255, 255, 0.15); margin-left: -18px; margin-right: -18px; margin-top: -18px;
                    padding-left: 18px; padding-right: 18px; padding-top: 18px;
                }
                .aio-title {
                    font-size: 17px; font-weight: 600; margin: 0; color: #fff;
                }
                .aio-controls {
                    display: flex; gap: 8px;
                }
                .aio-footer {
                    margin-top: 20px; padding-top: 10px; border-top: 1px solid rgba(255, 255, 255, 0.1);
                    text-align: center; font-size: 11px; color: rgba(255, 255, 255, 0.5);
                }
                .aio-footer .aio-heart {
                    color: #e44d4d; display: inline-block; animation: aio-heartbeat 1.5s infinite ease-in-out;
                }
                @keyframes aio-heartbeat {
                    0%, 100% { transform: scale(1); }
                    50% { transform: scale(1.2); }
                }
                .aio-button {
                    background-color: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.15); color: #e0e0e0;
                    padding: 6px 12px; border-radius: 8px; cursor: pointer; font-size: 12px;
                    transition: background-color 0.2s ease, border-color 0.2s ease, transform 0.1s ease; white-space: nowrap;
                }
                .aio-button:hover {
                    background-color: rgba(255, 255, 255, 0.18); border-color: rgba(255, 255, 255, 0.25);
                }
                .aio-button:active {
                    transform: scale(0.97);
                }
                .aio-section {
                    margin-bottom: 20px;
                }
                .aio-section-title {
                    font-size: 15px; font-weight: 600; color: #fff; margin: 0 0 10px 0; padding-bottom: 6px;
                    border-bottom: 1px solid rgba(255, 255, 255, 0.1);
                }
                .aio-content {
                    max-height: 300px; overflow-y: auto; padding-right: 5px;
                }
                .aio-list {
                    list-style: none; padding: 0; margin: 0;
                }
                .aio-item {
                    padding: 10px 12px; margin: 6px 0; background-color: rgba(255, 255, 255, 0.04);
                    border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 8px; font-size: 12px;
                    transition: background-color 0.2s ease, border-color 0.2s ease; position: relative;
                }
                .aio-item:hover {
                    background-color: rgba(255, 255, 255, 0.08); border-color: rgba(255, 255, 255, 0.15);
                }
                .aio-item-source, .aio-network-url {
                    color: #fff; word-break: break-all; margin-bottom: 5px; display: block;
                }
                .aio-item-info, .aio-network-details {
                    color: rgba(255, 255, 255, 0.65); font-size: 11px; display: flex; gap: 10px;
                    align-items: center; flex-wrap: wrap; margin-top: 6px;
                }
                .aio-item-tag {
                    background-color: rgba(255, 255, 255, 0.1); padding: 3px 7px; border-radius: 6px;
                    font-size: 10px; white-space: nowrap;
                }
                .aio-empty {
                    color: rgba(255, 255, 255, 0.6); font-style: italic; font-size: 13px;
                    text-align: center; padding: 20px 0;
                }
                .aio-filter {
                    width: 100%; box-sizing: border-box; padding: 9px 12px; margin-bottom: 12px;
                    background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.1);
                    border-radius: 8px; color: #fff; font-size: 13px;
                    transition: background-color 0.2s ease, border-color 0.2s ease;
                }
                .aio-filter::placeholder {
                    color: rgba(255, 255, 255, 0.5);
                }
                .aio-filter:focus {
                    outline: none; background: rgba(255, 255, 255, 0.12); border-color: rgba(100, 150, 255, 0.5);
                }
                .aio-link {

                }
                .aio-link .timestamp {
                    font-size: 10px; color: rgba(255, 255, 255, 0.5); margin-top: 4px; display: block;
                }
                .aio-iframe-item {
                    cursor: pointer;
                }
                .aio-network-method {
                    display: inline-block; padding: 3px 8px; border-radius: 6px; margin-right: 8px;
                    font-weight: bold; font-size: 10px; color: #fff;
                }
                .aio-network-method-get {
                    background-color: rgba(64, 156, 255, 0.3); border: 1px solid rgba(64, 156, 255, 0.5);
                }
                .aio-network-method-post {
                    background-color: rgba(50, 205, 50, 0.3); border: 1px solid rgba(50, 205, 50, 0.5);
                }
                .aio-network-method-put {
                    background-color: rgba(255, 165, 0, 0.3); border: 1px solid rgba(255, 165, 0, 0.5);
                }
                .aio-network-method-delete {
                    background-color: rgba(255, 69, 0, 0.3); border: 1px solid rgba(255, 69, 0, 0.5);
                }
                .aio-network-method-error {
                    background-color: rgba(200, 0, 0, 0.3); border: 1px solid rgba(200, 0, 0, 0.5);
                }
                .aio-network-controls {
                    margin-left: auto; display: flex; gap: 8px;
                }
                .aio-network-copy, .aio-network-show-payload {
                    background-color: rgba(147, 112, 219, 0.2); border: 1px solid rgba(147, 112, 219, 0.4);
                    color: #e0e0e0; padding: 3px 8px; border-radius: 6px; cursor: pointer; font-size: 10px;
                    transition: background-color 0.2s ease, border-color 0.2s ease; white-space: nowrap;
                }
                .aio-network-copy:hover, .aio-network-show-payload:hover {
                    background-color: rgba(147, 112, 219, 0.4); border-color: rgba(147, 112, 219, 0.6);
                }
                .aio-network-copy.copied {
                    background-color: rgba(50, 205, 50, 0.4); border-color: rgba(50, 205, 50, 0.6); color: #fff;
                }
                .aio-network-payload {
                    background-color: rgba(0, 0, 0, 0.3); padding: 10px; border-radius: 6px; margin-top: 10px;
                    font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; white-space: pre-wrap;
                    word-break: break-all; max-height: 150px; overflow-y: auto; display: none;
                    border: 1px solid rgba(255, 255, 255, 0.1);
                }
                .aio-image-item {

                }
                 .aio-image-button-group {
                    margin-left: auto; display: flex; gap: 6px;
                 }
                 .aio-image-copy, .aio-image-view, .aio-image-download {
                    border: 1px solid transparent; color: #e0e0e0; padding: 3px 8px; border-radius: 6px;
                    cursor: pointer; font-size: 10px; transition: background-color 0.2s ease, border-color 0.2s ease;
                    white-space: nowrap;
                 }
                 .aio-image-copy {
                    background-color: rgba(147, 112, 219, 0.2); border-color: rgba(147, 112, 219, 0.4);
                 }
                 .aio-image-view {
                    background-color: rgba(64, 156, 255, 0.2); border-color: rgba(64, 156, 255, 0.4);
                 }
                 .aio-image-download {
                    background-color: rgba(50, 205, 50, 0.2); border-color: rgba(50, 205, 50, 0.4);
                 }
                 .aio-image-copy:hover {
                    background-color: rgba(147, 112, 219, 0.4); border-color: rgba(147, 112, 219, 0.6);
                 }
                 .aio-image-view:hover {
                    background-color: rgba(64, 156, 255, 0.4); border-color: rgba(64, 156, 255, 0.6);
                 }
                 .aio-image-download:hover {
                    background-color: rgba(50, 205, 50, 0.4); border-color: rgba(50, 205, 50, 0.6);
                 }
                 .aio-image-copy.copied {
                    background-color: rgba(50, 205, 50, 0.4); border-color: rgba(50, 205, 50, 0.6); color: #fff;
                 }
                .aio-image-truncated {
                    cursor: pointer;
                }
                .aio-image-truncated:hover {
                    text-decoration: underline;
                }
                .aio-popup::-webkit-scrollbar, .aio-content::-webkit-scrollbar {
                    width: 8px;
                }
                .aio-popup::-webkit-scrollbar-track, .aio-content::-webkit-scrollbar-track {
                    background: rgba(255, 255, 255, 0.05); border-radius: 10px;
                }
                .aio-popup::-webkit-scrollbar-thumb, .aio-content::-webkit-scrollbar-thumb {
                    background: rgba(255, 255, 255, 0.2); border-radius: 10px; border: 2px solid transparent; background-clip: content-box;
                }
                .aio-popup::-webkit-scrollbar-thumb:hover, .aio-content::-webkit-scrollbar-thumb:hover {
                    background: rgba(255, 255, 255, 0.4);
                }
             `;
            const ss = document.createElement('style');
            ss.textContent = css;
            document.head.prepend(ss);
        }

        _mkTglBtn() {
            if (document.getElementById(this._T_ID)) return;
            this._tglBtn = document.createElement('button');
            this._tglBtn.id = this._T_ID;
            this._tglBtn.title = 'Toggle Scanner (Ctrl+Shift+A)';
            this._tglBtn.textContent = '🔍';
            this._tglBtn.className = 'aio-button';
            document.body.appendChild(this._tglBtn);
        }

        _mkPop() {
            if (document.getElementById(this._P_ID)) return;
            this._pop = document.createElement('div');
            this._pop.className = 'aio-popup';
            this._pop.id = this._P_ID;
            this._pop.innerHTML = `
                <div class="aio-header">
                    <h2 class="aio-title">All-in-One Web Scanner [BETA]</h2>
                    <div class="aio-controls">
                        <button class="aio-button" id="aio-clear" title="Clear all scanned data">Clear</button>
                        <button class="aio-button" id="aio-refresh" title="Rescan the page">Refresh</button>
                        <button class="aio-button" id="aio-close" title="Close Scanner (Ctrl+Shift+A)">✕</button>
                    </div>
                </div>
                ${this._genSecs()}
                <div class="aio-footer">
                    Made with <span class="aio-heart">❤️</span> by DARKIE | v${GM_info?.script?.version || '1.7.1'}
                </div>
            `;
            document.body.appendChild(this._pop);
        }

        _genSecs() {
            const secs = [{
                id: 'stream-section',
                title: 'Stream Files',
                contentId: 'stream-content',
                hasFilter: false,
                generator: this._genStrmSecs.bind(this)
            }, {
                id: 'iframe-section',
                title: 'Iframe Sources',
                contentId: 'iframe-content',
                hasFilter: false,
                generator: this._genEmpty.bind(this, 'iframes')
            }, {
                id: 'network-section',
                title: 'Network Requests',
                contentId: 'network-content',
                hasFilter: true,
                filterPlaceholder: 'Filter requests by URL...',
                generator: this._genEmpty.bind(this, 'network requests')
            }, {
                id: 'image-section',
                title: 'Image Sources',
                contentId: 'image-content',
                hasFilter: true,
                filterPlaceholder: 'Filter images by URL...',
                generator: this._genEmpty.bind(this, 'images')
            }, ];
            return secs.map(s => `
                <div class="aio-section" id="${s.id}">
                    <h3 class="aio-section-title">${s.title}</h3>
                    ${s.hasFilter ? `<input type="text" class="aio-filter" id="${s.contentId}-filter" placeholder="${s.filterPlaceholder}">` : ''}
                    <div class="aio-content" id="${s.contentId}">
                        ${s.generator()}
                    </div>
                </div>
            `).join('');
        }

        _genStrmSecs() {
            return this._cfg.linkTypes.map(t => `
                <div class="aio-subsection" id="${t}-section">
                    <h4 class="aio-section-subtitle" style="font-size: 13px; margin-bottom: 5px; color: #ccc;">${t.toUpperCase()}</h4>
                    <div class="aio-list-container" id="${t}-content">
                         <div class="aio-empty">No ${t.toUpperCase()} files detected yet...</div>
                    </div>
                </div>
            `).join('');
        }

        _mkPrvPop() {
            if (document.getElementById(this._PR_ID)) return;
            this._prvPop = document.createElement('div');
            this._prvPop.className = 'aio-image-preview';
            this._prvPop.id = this._PR_ID;
            this._prvPop.innerHTML = '<img src="" alt="Preview">';
            document.body.appendChild(this._prvPop);
        }

        _cacheDOM() {
            this._pop = document.getElementById(this._P_ID);
            this._tglBtn = document.getElementById(this._T_ID);
            this._prvPop = document.getElementById(this._PR_ID);
            if (!this._pop || !this._tglBtn || !this._prvPop) {
                return false;
            }
            this._clrBtn = this._pop.querySelector('#aio-clear');
            this._clsBtn = this._pop.querySelector('#aio-close');
            this._refBtn = this._pop.querySelector('#aio-refresh');
            this._netFltIn = this._pop.querySelector('#network-content-filter');
            this._imgFltIn = this._pop.querySelector('#image-content-filter');
            return true;
        }

        _addEvts() {
            this._clsBtn ?.addEventListener('click', () => this._hidePop());
            this._clrBtn ?.addEventListener('click', () => this._clrAll());
            this._refBtn ?.addEventListener('click', () => this._scanAll());
            this._tglBtn ?.addEventListener('click', () => this._tglPop());
            this._netFltIn ?.addEventListener('input', this._dbnc(this._hdlNetFlt.bind(this), 300));
            this._imgFltIn ?.addEventListener('input', this._dbnc(this._hdlImgFlt.bind(this), 300));
        }

        _setupKB() {
            if (window.aioScannerKBLstnr) return;
            document.addEventListener('keydown', (e) => {
                if (e.ctrlKey && e.shiftKey && (e.key === 'A' || e.key === 'a')) {
                    e.preventDefault();
                    this._tglPop();
                }
            });
            window.aioScannerKBLstnr = true;
        }

        _setupDelEvts() {
            if (!this._pop || window.aioScannerDelLstnr) return;

            this._pop.addEventListener('click', (evt) => {
                const sl = evt.target.closest('.aio-link');
                if (sl) {
                    this._hdlStrmCp(sl);
                    return;
                }
                const ifi = evt.target.closest('.aio-iframe-item');
                if (ifi) {
                    this._hdlIfrCp(ifi);
                    return;
                }
                const ni = evt.target.closest('.aio-network-item');
                if (ni) {
                    if (evt.target.closest('.aio-network-copy')) {
                        this._hdlNetCp(evt.target.closest('.aio-network-copy'));
                    } else if (evt.target.closest('.aio-network-show-payload')) {
                        this._hdlNetPTgl(evt.target.closest('.aio-network-show-payload'));
                    }
                    return;
                }
                const imi = evt.target.closest('.aio-image-item');
                if (imi) {
                    if (evt.target.closest('.aio-image-copy')) {
                        this._hdlImgCp(evt.target.closest('.aio-image-copy'));
                    } else if (evt.target.closest('.aio-image-view')) {
                        this._hdlImgVw(evt.target.closest('.aio-image-view'));
                    } else if (evt.target.closest('.aio-image-download')) {
                        this._hdlImgDl(evt.target.closest('.aio-image-download'));
                    } else if (evt.target.closest('.aio-image-truncated')) {
                        this._hdlImgTrunClk(evt.target.closest('.aio-image-truncated'));
                    }
                    return;
                }
            });

            const imgCont = this._pop.querySelector(this._cfg.selectors.image);
            if (imgCont) {
                imgCont.addEventListener('mouseover', (evt) => {
                    const imi = evt.target.closest('.aio-image-item');
                    if (imi) {
                        this._hdlImgPrvShow(imi);
                    }
                });
                imgCont.addEventListener('mouseout', (evt) => {
                    const imi = evt.target.closest('.aio-image-item');
                    const rt = evt.relatedTarget;
                    if (imi && !imi.contains(rt) && !this._prvPop ?.contains(rt)) {
                        this._hdlImgPrvHide();
                    }
                });
            }
            window.aioScannerDelLstnr = true;
        }

        // --- Event Handlers ---

        _hdlStrmCp(el) {
            const u = el.dataset.url;
            if (u) {
                this._cpHlp(u, el);
            }
        }

        async _hdlIfrCp(el) {
            const fs = el.dataset.src;
            if (fs) {
                this._cpHlp(fs, el);
            } else {
                console.warn("AIO Scanner (Short): Could not find data-src on iframe item:", el);
                this._flashItm(el, false);
            }
        }

        async _hdlNetCp(btn) {
            const i = btn.dataset.index;
            if (i !== undefined && this._reqs[i]) {
                const u = this._reqs[i].url;
                const ok = await this._cpClip(u);
                this._flashBtn(btn, ok, 'Copied!', 'Copy URL');
            }
        }

        _hdlNetPTgl(btn) {
            const i = btn.dataset.index;
            if (i !== undefined && this._pop) {
                const pDiv = this._pop.querySelector(`#payload-${i}`);
                if (pDiv) {
                    const v = pDiv.style.display === 'block';
                    pDiv.style.display = v ? 'none' : 'block';
                    btn.textContent = v ? 'Payload' : 'Hide';
                }
            }
        }

        async _hdlImgCp(btn) {
            const itm = btn.closest('.aio-image-item');
            if (itm) {
                const s = itm.dataset.src;
                const ok = await this._cpClip(s);
                this._flashBtn(btn, ok, 'Copied!', 'Copy');
            }
        }

        _hdlImgVw(btn) {
            const itm = btn.closest('.aio-image-item');
            if (itm) {
                const s = itm.dataset.src;
                window.open(s, '_blank');
            }
        }

        _hdlImgDl(btn) {
            const itm = btn.closest('.aio-image-item');
            if (itm) {
                const s = itm.dataset.src;
                const fn = s.split('/').pop().split(/[?#]/)[0] || 'image';
                this._dlImg(s, fn);
            }
        }

        _hdlImgTrunClk(srcEl) {
            const fu = srcEl.title;
            if (srcEl.textContent === fu) {
                srcEl.textContent = this._truncUrl(fu);
            } else {
                srcEl.textContent = fu;
            }
        }

        _hdlImgPrvShow(itm) {
            if (!this._prvPop) return;
            const s = itm.dataset.src;
            const prv = this._prvPop;
            const img = prv.querySelector('img');
            img.src = s;

            const r = itm.getBoundingClientRect();
            const pW = this._prvMaxW;
            const pL = r.left - pW - this._prvPad;

            if (pL >= 0) {
                prv.style.left = `${pL}px`;
                prv.style.right = 'auto';
            } else {
                const pR = r.right + this._prvPad;
                if (pR + pW <= window.innerWidth) {
                    prv.style.left = `${pR}px`;
                    prv.style.right = 'auto';
                } else {
                    prv.style.left = `${this._prvPad}px`;
                    prv.style.right = 'auto';
                }
            }

            const pT = r.top;
            const vh = window.innerHeight;
            const pH = prv.offsetHeight || 200;
            if (pT + pH > vh - this._prvPad) {
                prv.style.top = `${Math.max(0, vh - pH - this._prvPad)}px`;
            } else {
                prv.style.top = `${Math.max(0, pT)}px`;
            }

            prv.style.display = 'block';
            requestAnimationFrame(() => prv.style.opacity = '1');
        }

        _hdlImgPrvHide() {
            if (!this._prvPop) return;
            this._prvPop.style.opacity = '0';
            setTimeout(() => {
                if (this._prvPop && this._prvPop.style.opacity === '0') {
                    this._prvPop.style.display = 'none';
                }
            }, 200);
        }

        _hdlNetFlt(evt) {
            if (!this._pop) return;
            const f = evt.target.value.toLowerCase();
            const nc = this._pop.querySelector(this._cfg.selectors.network);
            if (!nc) return;
            nc.querySelectorAll('.aio-network-item').forEach(itm => {
                const ue = itm.querySelector('.aio-network-url');
                const u = ue ? (ue.title || ue.textContent).toLowerCase() : '';
                itm.style.display = u.includes(f) ? 'block' : 'none';
            });
        }

        _hdlImgFlt(evt) {
            if (!this._pop) return;
            const f = evt.target.value.toLowerCase();
            const ic = this._pop.querySelector(this._cfg.selectors.image);
            if (!ic) return;
            ic.querySelectorAll('.aio-image-item').forEach(itm => {
                const s = itm.dataset.src ? itm.dataset.src.toLowerCase() : '';
                itm.style.display = s.includes(f) ? 'flex' : 'none';
            });
        }

        // --- State Management & SPA Handling ---

        _svSt(isOpen) {
            try {
                sessionStorage.setItem(this._SK, JSON.stringify({
                    isOpen
                }));
            } catch (e) {
                console.warn("AIO Scanner: Failed to save state to sessionStorage.", e);
            }
        }

        _ldSt() {
            try {
                const st = sessionStorage.getItem(this._SK);
                return st ? JSON.parse(st) : {
                    isOpen: false
                };
            } catch (e) {
                console.warn("AIO Scanner: Failed to load state from sessionStorage. Resetting.", e);
                sessionStorage.removeItem(this._SK);
                return {
                    isOpen: false
                };
            }
        }

        _chkStUI() {
            if (!this._pop || !this._tglBtn) {
                this._ensureUI();
                return;
            }
            const st = this._ldSt();
            const shouldVis = st.isOpen;
            const isVis = this._pop.classList.contains('aio-visible');

            if (shouldVis && !isVis) {
                this._showPop();
            } else if (!shouldVis && isVis) {
                this._hidePop();
            } else if (!shouldVis && !isVis) {
                this._pop.style.display = 'none';
                this._tglBtn.style.display = 'block';
                if (this._prvPop) {
                    this._prvPop.style.display = 'none';
                    this._prvPop.style.opacity = '0';
                }
            }
        }

        _listenSPA() {
            if (window.aioScannerNavLstnr) return;

            const navHdl = () => {
                this._ensureUI();
                setTimeout(() => this._chkStUI(), 50);
                setTimeout(() => this._scanIfNeed(), 150);
            };

            window.addEventListener('popstate', navHdl);
            window.addEventListener('hashchange', navHdl);

            const self = this;

            function wrapHist(m) {
                const orig = history[m];
                if (!orig) return;
                try {
                    history[m] = function(st, t, u) {
                        let res;
                        try {
                            res = orig.apply(this, arguments);
                        } catch (e) {
                            console.error(`AIO Scanner: Error in original history.${m}`, e);
                            throw e;
                        }
                        try {
                            const evt = new CustomEvent('historystatechange', {
                                detail: {
                                    url: u,
                                    state: st,
                                    title: t,
                                    method: m
                                }
                            });
                            window.dispatchEvent(evt);
                        } catch (e) {
                            console.warn("AIO Scanner: CustomEvent dispatch failed, using direct handler.", e);
                            navHdl();
                        }
                        return res;
                    };
                } catch (e) {
                    console.error(`AIO Scanner: Failed to wrap history.${m}`, e);
                }
            }

            wrapHist('pushState');
            wrapHist('replaceState');
            window.addEventListener('historystatechange', navHdl);

            window.aioScannerNavLstnr = true;
            console.log("AIO Scanner (Short): SPA Navigation listener attached.");
        }

        _scanIfNeed() {
            const popEl = document.getElementById(this._P_ID);
            if (popEl ?.classList.contains('aio-visible')) {
                this._scanAll();
            }
        }

        _tglPop() {
            if (!this._pop) {
                this._ensureUI();
                return;
            }
            const isVis = this._pop.classList.contains('aio-visible');
            if (isVis) {
                this._hidePop();
            } else {
                this._showPop();
                this._scanAll();
            }
        }

        _showPop() {
            if (!this._pop || !this._tglBtn) {
                this._ensureUI();
                return;
            }
            this._pop.style.display = 'block';
            requestAnimationFrame(() => {
                requestAnimationFrame(() => {
                    if (this._pop) this._pop.classList.add('aio-visible');
                });
            });
            this._tglBtn.style.display = 'none';
            this._svSt(true);
        }

        _hidePop() {
            if (!this._pop || !this._tglBtn) {
                this._ensureUI();
                return;
            }
            this._pop.classList.remove('aio-visible');
            setTimeout(() => {
                if (this._pop) this._pop.style.display = 'none';
                if (this._prvPop) {
                    this._prvPop.style.display = 'none';
                    this._prvPop.style.opacity = '0';
                }
                if (this._tglBtn) this._tglBtn.style.display = 'block';
            }, 200);
            this._svSt(false);
        }

        _obsDOM() {
            if (this._obs) {
                this._obs.disconnect();
            }
            if (!document.body) {
                console.warn("AIO Scanner: Cannot observe DOM, document.body not ready.");
                return;
            }

            try {
                this._obs = new MutationObserver(muts => {
                    let changed = false;
                    let removed = false;
                    for (const m of muts) {
                        if (m.type === 'childList') {
                            if (m.addedNodes.length > 0) {
                                changed = true;
                                m.addedNodes.forEach(n => {
                                    if (n.nodeType === Node.ELEMENT_NODE) {
                                        this._scanElStrms(n);
                                    }
                                });
                            }
                            if (m.removedNodes.length > 0) {
                                for (const n of m.removedNodes) {
                                    if (n.id === this._P_ID || n.id === this._T_ID || n.id === this._PR_ID) {
                                        removed = true;
                                        break;
                                    }
                                }
                            }
                        } else if (m.type === 'attributes') {
                            changed = true;
                            const tgt = m.target;
                            const attr = m.attributeName;
                            if (tgt && attr) {
                                const val = tgt.getAttribute(attr);
                                if (typeof val === 'string' && ['src', 'href', 'data', 'data-src', 'data-url', 'style'].includes(attr.toLowerCase())) {
                                    if (attr.toLowerCase() === 'style') {
                                        this._debScanAll();
                                    } else {
                                        this._hdlStrmUrl(val, `mutAttr-${attr}`);
                                    }
                                }
                            }
                        }
                        if (removed) break;
                    }

                    if (removed) {
                        console.warn("AIO Scanner: UI element removed, attempting to re-initialize.");
                        this._ensureUI();
                    } else {
                        this._debEnsureUI();
                    }
                });

                this._obs.observe(document.body, {
                    childList: true,
                    subtree: true,
                    attributes: true,
                    attributeFilter: ['src', 'style', 'data-src', 'data-url', 'href', 'data']
                });
            } catch (e) {
                console.error("AIO Scanner: Failed to create or attach MutationObserver.", e);
            }
        }

        _ensureUI() {
            const tglMiss = !document.getElementById(this._T_ID);
            const popMiss = !document.getElementById(this._P_ID);
            const prvMiss = !document.getElementById(this._PR_ID);

            if (tglMiss || popMiss || prvMiss) {
                console.log("AIO Scanner: UI element missing, re-initializing UI.");
                this._initd = false;
                this._initUIObs();
                this._chkStUI();
            }
        }

        /* ==========================================================================
           Scanning Logic
           ========================================================================== */

        _scanAll() {
            const now = Date.now();
            if (this._lastScanTime && (now - this._lastScanTime < 200)) {
                return;
            }
            this._lastScanTime = now;

            try {
                this._scanVidSrc();
                this._scanIfr();
                this._updNetDisp();
                this._scanImgs();
                this._scanInline();
            } catch (err) {
                console.error("AIO Scanner: Error during full scan:", err);
            }
        }

        _scanVidSrc() {
            try {
                document.querySelectorAll(
                    'video, source, track, object, embed, a, [data-src], [data-url], [data-stream-url], [data-hls-url], [data-mp4-url]'
                ).forEach(el => {
                    const urls = [
                        el.src,
                        el.currentSrc,
                        el.href,
                        el.data,
                        el.dataset ?.src,
                        el.dataset ?.url,
                        el.getAttribute('data-stream-url'),
                        el.getAttribute('data-hls-url'),
                        el.getAttribute('data-mp4-url')
                    ].filter(Boolean);

                    urls.forEach(u => {
                        if (typeof u === 'string') {
                            this._hdlStrmUrl(u, `tag-${el.tagName.toLowerCase()}`);
                        }
                    });

                    if (el.tagName === 'TRACK' && el.default && el.src) {
                        this._hdlStrmUrl(el.src, 'tag-track-default');
                    }
                });
            } catch (err) {
                console.error("AIO Scanner: Error scanning video/source elements:", err);
            }
        }

        _isPotStrmUrl(u) {
            if (typeof u !== 'string') return false;
            const lu = u.toLowerCase();
            const cu = lu.split(/[?#]/)[0];

            if (/\.(m3u8|mp4|vtt|srt|mpd)$/i.test(cu)) return true;
            if (/\/hls\/|\/dash\/|\/manifest\/|\/playlist\.m3u8|\/master\.m3u8|\/video\.mp4|\.mpd/i.test(lu)) return true;
            if (/[?&](format|type|ext)=(m3u8|mp4|vtt|srt|mpd)/i.test(lu)) return true;
            if (lu.startsWith('blob:')) return true;

            return false;
        }

        _detStrmTyp(u, m = null) {
            if (typeof u !== 'string') return null;
            const lu = u.toLowerCase();

            if (m) {
                const lm = m.toLowerCase().split(';')[0].trim();
                if (this._STRM_MIME[lm]) {
                    return this._STRM_MIME[lm];
                }
            }

            for (const t of [...this._cfg.linkTypes, 'mpd']) {
                if (lu.includes(`.${t}`)) {
                    return t;
                }
            }

            if (lu.includes('m3u8') || lu.includes('/hls/') || lu.includes('/manifest/')) return 'm3u8';
            if (lu.includes('.mpd') || lu.includes('/dash/')) return 'mpd';
            if (lu.includes('mp4') || lu.includes('/video/')) return 'mp4';
            if (lu.includes('vtt') || lu.includes('/caption')) return 'vtt';
            if (lu.includes('srt') || lu.includes('/subtitle')) return 'srt';
            if (lu.startsWith('blob:')) return 'mp4';

            return null;
        }

        _hdlStrmUrl(u, srcT = 'unknown', m = null) {
            if (typeof u !== 'string' || u.trim() === '' || u.startsWith('javascript:')) {
                return;
            }
            const absU = this._absUrl(u);
            if (!absU) return;

            if (!this._isPotStrmUrl(absU)) {
                return;
            }

            const type = this._detStrmTyp(absU, m);
            if (!type || !this._cfg.linkTypes.includes(type)) {
                return;
            }

            const clnU = this._clnUrl(absU);
            if (!this._urls.has(clnU)) {
                this._urls.add(clnU);
                this._addStrmLnk(type, absU);
            }
        }

        _scanInline() {
            try {
                document.querySelectorAll('script').forEach(scr => {
                    if (!scr.hasAttribute('src') && scr.textContent) {
                        const cont = scr.textContent;
                        let m;
                        this._RGX_STRM.lastIndex = 0;
                        while ((m = this._RGX_STRM.exec(cont)) !== null) {
                            const fUrl = m[1] || m[3];
                            if (fUrl) {
                                this._hdlStrmUrl(fUrl, 'script');
                            }
                        }
                    }
                });
            } catch (err) {
                console.error("AIO Scanner: Error scanning inline scripts:", err);
            }
        }

        _monBlob() {
            if (window.aioBlobMon || typeof URL === 'undefined' || !URL.createObjectURL) return;

            const self = this;
            const origC = URL.createObjectURL;

            try {
                URL.createObjectURL = function(obj) {
                    const res = origC.apply(this, arguments);
                    if (obj instanceof Blob && obj.type) {
                        const lt = obj.type.toLowerCase();
                        const st = Object.entries(self._STRM_MIME).find(([m]) => lt.startsWith(m)) ?. [1];
                        if (st) {
                            // Blob found, potentially useful for debugging, but rely on src assignment
                        }
                    }
                    return res;
                };
                window.aioBlobMon = true;
                console.log("AIO Scanner (Short): Blob URL monitor attached.");
            } catch (err) {
                console.error("AIO Scanner: Failed to monitor Blob URLs, reverting.", err);
                URL.createObjectURL = origC;
            }
        }

        _scanElStrms(el) {
            if (!el || typeof el.querySelectorAll !== 'function') return;

            try {
                const urlsS = [
                    el.src,
                    el.href,
                    el.data,
                    el.dataset ?.src,
                    el.dataset ?.url,
                    el.getAttribute('data-stream-url')
                ].filter(Boolean);
                urlsS.forEach(u => this._hdlStrmUrl(u, `added-${el.tagName.toLowerCase()}`));

                el.querySelectorAll(
                    'video, source, track, object, embed, a, [data-src], [data-url], [data-stream-url]'
                ).forEach(ch => {
                    const urlsC = [
                        ch.src,
                        ch.currentSrc,
                        ch.href,
                        ch.data,
                        ch.dataset ?.src,
                        ch.dataset ?.url,
                        ch.getAttribute('data-stream-url')
                    ].filter(Boolean);
                    urlsC.forEach(u => this._hdlStrmUrl(u, `added-child-${ch.tagName.toLowerCase()}`));
                    if (ch.tagName === 'TRACK' && ch.default && ch.src) {
                        this._hdlStrmUrl(ch.src, 'added-track-default');
                    }
                });
            } catch (err) {
                console.error("AIO Scanner: Error scanning added element streams:", err);
            }
        }

        _scanIfr() {
            try {
                const ifrs = Array.from(document.getElementsByTagName('iframe'));
                const ifrInfos = ifrs.map(ifr => this._extIfrInf(ifr));

                const embs = Array.from(document.getElementsByTagName('embed'));
                const objs = Array.from(document.getElementsByTagName('object'));
                const frms = Array.from(document.getElementsByTagName('frame'));

                const addSrcs = [...embs, ...objs, ...frms]
                    .map(el => ({
                        src: el.src || el.data,
                        type: el.tagName.toLowerCase(),
                        attributes: []
                    }))
                    .filter(inf => inf.src);

                const allSrcs = [...ifrInfos, ...addSrcs]
                    .map(inf => ({ ...inf,
                        src: this._absUrl(inf.src)
                    }))
                    .filter(inf => inf.src && !inf.src.startsWith('about:blank') && !inf.src.startsWith('javascript:'));

                const uniqSrcs = Array.from(new Map(allSrcs.map(itm => [itm.src, itm])).values());

                const cont = this._pop ?.querySelector(this._cfg.selectors.iframe);
                if (cont) {
                    cont.innerHTML = uniqSrcs.length > 0 ?
                        this._genIfrList(uniqSrcs) :
                        this._genEmpty('iframes');
                }
            } catch (err) {
                console.error("AIO Scanner: Error scanning iframes/embeds:", err);
            }
        }

        _extIfrInf(ifr) {
            let s = ifr.src || ifr.dataset.src;
            let t = ifr.src ? 'direct' : (ifr.dataset.src ? 'lazy' : 'unknown');
            if (!s && ifr.srcdoc) {
                s = 'srcdoc';
                t = 'srcdoc';
            }
            const a = [];
            if (ifr.allow) a.push('allow: ' + ifr.allow.substring(0, 30) + (ifr.allow.length > 30 ? '...' : ''));
            if (ifr.sandbox) a.push('sandbox');
            if (ifr.loading) a.push('loading: ' + ifr.loading);
            if (ifr.name) a.push('name: ' + ifr.name);
            if (ifr.width || ifr.height) a.push(`size: ${ifr.width || '?'}x${ifr.height || '?'}`);

            return {
                src: s || '',
                type: t,
                attributes: a
            };
        }

        _scanImgs() {
            try {
                const imgs = Array.from(document.querySelectorAll('img'));
                const imgInfos = imgs
                    .map(img => this._extImgInf(img))
                    .filter(inf => inf.src && !inf.src.startsWith('data:'));

                const els = document.getElementsByTagName('*');
                const bgImgs = Array.from(els)
                    .map(el => {
                        try {
                            const st = window.getComputedStyle(el);
                            if (!st || !st.backgroundImage || st.backgroundImage === 'none') {
                                return [];
                            }
                            const urls = (st.backgroundImage.match(/url\(['"]?(.*?)['"]?\)/g) || [])
                                .map(m => m.replace(/url\(['"]?(.*?)['"]?\)/g, '$1'))
                                .filter(u => u && !u.startsWith('data:'));

                            return urls.map(u => ({
                                src: this._absUrl(u),
                                type: 'bg',
                                attributes: [`el: ${el.tagName.toLowerCase()}${el.id ? '#' + el.id : ''}${el.className && typeof el.className ==='string' ? '.' + el.className.split(' ').filter(Boolean).join('.') : ''}`]
                            }));
                        } catch (e) {
                            return [];
                        }
                    })
                    .flat()
                    .filter(Boolean);

                const combSrcs = [...imgInfos, ...bgImgs];

                const uniqSrcs = Array.from(new Map(combSrcs.map(itm => [itm.src, itm])).values())
                    .filter(itm => itm.src);

                const cont = this._pop ?.querySelector(this._cfg.selectors.image);
                if (cont) {
                    cont.innerHTML = uniqSrcs.length > 0 ?
                        this._genImgList(uniqSrcs) :
                        this._genEmpty('images');
                    if (this._imgFltIn) {
                        this._hdlImgFlt({
                            target: this._imgFltIn
                        });
                    }
                }
            } catch (err) {
                console.error("AIO Scanner: Error scanning images:", err);
            }
        }

        _extImgInf(img) {
            const s = img.currentSrc || img.src;
            const a = [];
            if (img.alt) a.push(`alt: ${img.alt.substring(0, 30)}${img.alt.length > 30 ? '...' : ''}`);
            if (img.naturalWidth > 0 || img.naturalHeight > 0) {
                a.push(`actual: ${img.naturalWidth}x${img.naturalHeight}`);
            } else if (img.width || img.height) {
                a.push(`attr: ${img.width}x${img.height}`);
            }
            if (img.loading) a.push(`loading: ${img.loading}`);
            if (img.srcset) a.push('srcset');

            const absS = this._absUrl(s);
            const ext = absS ?.split('.').pop().split(/[#?]/)[0];
            if (ext) a.push(ext.toLowerCase());

            return {
                src: absS,
                type: 'img',
                attributes: a
            };
        }

        /* ==========================================================================
           Network Monitoring
           ========================================================================== */

        _setupNetMon() {
            if (window.aioNetMon) return;

            const self = this;

            try {
                const oF = window.fetch;
                window.fetch = async function(...args) {
                    const req = args[0];
                    const opts = args[1] || {};
                    const u = typeof req === 'string' ? req : req ?.url;
                    const m = opts.method || (typeof req === 'object' ? req ?.method : 'GET') || 'GET';
                    const sT = Date.now();
                    let pld = null;

                    try {
                        if (opts.body) {
                            pld = typeof opts.body === 'string' ? opts.body : '[Non-String Body]';
                        } else if (req instanceof Request && req.body) {
                            pld = await req.clone().text().catch(() => '[Stream Body]');
                        }
                    } catch (payloadError) {
                        pld = '[Error Reading Body]';
                        console.warn("AIO Scanner: Error reading fetch request body:", payloadError);
                    }

                    self._hdlStrmUrl(u, 'net-fetch-req');

                    try {
                        const res = await oF.apply(this, args);
                        const eT = Date.now();
                        const ct = res.headers.get('Content-Type');

                        if (ct) {
                            self._hdlStrmUrl(u, 'net-fetch-resp', ct);
                        }

                        self._addNetReq({
                            url: self._absUrl(u),
                            method: m.toUpperCase(),
                            status: res.status,
                            duration: eT - sT,
                            payload: pld,
                            headers: self._hdrsToObj(res.headers),
                            timestamp: new Date().toISOString(),
                            type: 'fetch'
                        });
                        return res;
                    } catch (err) {
                        self._addNetReq({
                            url: self._absUrl(u),
                            method: m.toUpperCase(),
                            status: 'ERROR',
                            error: err.message || String(err),
                            payload: pld,
                            timestamp: new Date().toISOString(),
                            type: 'fetch'
                        });
                        throw err;
                    }
                };
            } catch (e) {
                console.error("AIO Scanner: Failed to patch window.fetch.", e);
            }

            try {
                const X = XMLHttpRequest.prototype;
                const oO = X.open;
                const oS = X.send;
                if (!oO || !oS) throw new Error("XHR methods missing");

                X.open = function(m, u) {
                    this._reqURL = self._absUrl(u);
                    this._reqMeth = m;
                    this._startT = Date.now();
                    self._hdlStrmUrl(this._reqURL, 'net-xhr-req');
                    oO.apply(this, arguments);
                };

                X.send = function(b) {
                    if (this._aioLdLstnr) {
                        this.removeEventListener('loadend', this._aioLdLstnr);
                    }

                    this._aioLdLstnr = () => {
                        const eT = Date.now();
                        let sP = null;

                        if (b) {
                            try {
                                if (b instanceof Document) sP = '[Document Payload]';
                                else if (b instanceof Blob) sP = `[Blob Payload: ${b.type}, Size: ${b.size}]`;
                                else if (b instanceof ArrayBuffer) sP = `[ArrayBuffer Payload: ${b.byteLength} bytes]`;
                                else if (b instanceof FormData) sP = '[FormData Payload]';
                                else if (typeof b === 'string') sP = b.substring(0, 5000) + (b.length > 5000 ? '...' : '');
                                else sP = '[Unknown Payload Type]';
                            } catch (e) {
                                sP = '[Error Reading Payload]';
                                console.warn("AIO Scanner: Error reading XHR send payload:", e);
                            }
                        }

                        const ct = this.getResponseHeader('Content-Type');
                        if (ct) {
                            self._hdlStrmUrl(this._reqURL, 'net-xhr-resp', ct);
                        }

                        self._addNetReq({
                            url: this._reqURL,
                            method: (this._reqMeth || 'GET').toUpperCase(),
                            status: this.status,
                            duration: eT - (this._startT || eT),
                            payload: sP,
                            headers: self._parseHdrs(this.getAllResponseHeaders()),
                            timestamp: new Date().toISOString(),
                            type: 'xhr',
                            error: (this.status < 200 || this.status >= 400) ? `HTTP ${this.status}` : (this.status === 0 && !this.response ? 'Network Error' : null)
                        });
                    };

                    this.addEventListener('loadend', this._aioLdLstnr);
                    oS.apply(this, arguments);
                };
            } catch (e) {
                console.error("AIO Scanner: Failed to patch XMLHttpRequest.", e);
            }

            window.aioNetMon = true;
            console.log("AIO Scanner (Short): Network monitoring (fetch/XHR) attached.");
        }

        _addNetReq(req) {
            if (!req || !req.url || req.url.startsWith('data:') ||
                req.url.startsWith('chrome-extension:') || req.url.startsWith('moz-extension:')) {
                return;
            }
            this._reqs.unshift(req);
            if (this._reqs.length > this._maxR) {
                this._reqs.pop();
            }
            if (this._pop ?.classList.contains('aio-visible')) {
                this._updNetDisp();
            }
        }

        _updNetDisp() {
            const cont = this._pop ?.querySelector(this._cfg.selectors.network);
            if (!cont) return;

            let list = cont.querySelector('.aio-list');
            const es = cont.querySelector('.aio-empty');

            if (this._reqs.length === 0) {
                if (!es) {
                    cont.innerHTML = this._genEmpty('network requests');
                }
                if (list) list.innerHTML = '';
                return;
            } else {
                if (!list) list = this._mkList(cont);
                if (es) es.remove();
            }

            const existingKeys = new Set(Array.from(list.children).map(li => li.dataset.requestKey));
            let added = 0;

            for (let i = 0; i < this._reqs.length; i++) {
                const r = this._reqs[i];
                const key = `${r.url}_${r.timestamp}`;
                if (!existingKeys.has(key)) {
                    const ni = this._mkNetLi(r, i, key);
                    list.prepend(ni);
                    added++;
                }
            }

            const curCnt = list.children.length;
            if (curCnt > this._maxR + 10) {
                while (list.children.length > this._maxR) {
                    list.removeChild(list.lastChild);
                }
            }

            if (added > 0 || !this._netFiltApp) {
                if (this._netFltIn) {
                    this._hdlNetFlt({
                        target: this._netFltIn
                    });
                    this._netFiltApp = true;
                }
            }
        }

        _mkNetLi(r, i, key) {
            const li = document.createElement('li');
            li.className = 'aio-item aio-network-item';
            li.dataset.requestKey = key;
            li.dataset.url = r.url;

            const methodClass = r.status === 'ERROR' || r.status >= 400 ?
                'error' :
                r.method.toLowerCase();
            const statusColor = r.status >= 400 || r.status === 'ERROR' ? '#ff8a8a' : '#a6e22e';
            const statusText = `${r.status}${r.error ? ` (${r.error.substring(0,30)}...)` : ''}`;

            li.innerHTML = `
                <div>
                    <span class="aio-network-method aio-network-method-${methodClass}">${r.method}</span>
                    <span class="aio-network-url" title="${r.url}">${this._truncUrl(r.url)}</span>
                </div>
                <div class="aio-network-details">
                    <span>Status: <strong style="color: ${statusColor}">${statusText}</strong></span>
                    ${r.duration !== undefined ? `<span>${r.duration}ms</span>` : ''}
                    <span>${new Date(r.timestamp).toLocaleTimeString()}</span>
                    <div class="aio-network-controls">
                        ${r.payload ? `<button class="aio-network-show-payload" data-index="${i}" title="Show/Hide Request Payload">Payload</button>` : ''}
                        <button class="aio-network-copy" data-index="${i}" title="Copy URL">Copy URL</button>
                    </div>
                </div>
                ${r.payload ? `
                    <div class="aio-network-payload" id="payload-${i}">
                        <pre><code>${this._escHtml(r.payload.substring(0, 5000))} ${r.payload.length > 5000 ? '...' : ''}</code></pre>
                    </div>` : ''}
            `;
            return li;
        }


        /* ==========================================================================
           UI Updates & List Generation
           ========================================================================== */

        _addStrmLnk(type, url) {
            const cont = this._pop ?.querySelector(`#${type}-content`);
            if (!cont) return;

            const list = cont.querySelector('.aio-list') || this._mkList(cont);
            const es = cont.querySelector('.aio-empty');
            if (es) es.remove();

            const li = document.createElement('li');
            li.className = 'aio-item aio-link';
            li.dataset.url = url;
            li.title = `Click to copy: ${url}`;
            li.innerHTML = `
                <span class="aio-item-source">${this._truncUrl(url)}</span>
                <div class="aio-item-info">
                    <span class="timestamp">Detected: ${new Date().toLocaleTimeString()}</span>
                </div>
            `;
            list.prepend(li);
        }

        _genIfrList(srcs) {
            return `<ul class="aio-list">${srcs.map(inf => `
                <li class="aio-item aio-iframe-item" data-src="${this._escHtml(inf.src)}" title="Click to copy source: ${inf.src}">
                    <span class="aio-item-source" title="${inf.src}">${this._truncUrl(inf.src)}</span>
                    <div class="aio-item-info">
                        <span class="aio-item-tag">${inf.type}</span>
                        ${inf.attributes.map(a => `<span class="aio-item-tag" title="${a}">${a.substring(0, 40)}${a.length > 40 ? '...' : ''}</span>`).join('')}
                    </div>
                </li>`).join('')}</ul>`;
        }

        _genImgList(srcs) {
            return `<ul class="aio-list">${srcs.map((inf, i) => `
                <li class="aio-item aio-image-item" data-src="${inf.src}">
                    <span class="aio-item-source aio-image-truncated" title="${inf.src}">${this._truncUrl(inf.src)}</span>
                    <div class="aio-item-info">
                        <span class="aio-item-tag">${inf.type}</span>
                        ${inf.attributes.map(a => `<span class="aio-item-tag" title="${a}">${a.substring(0, 40)}${a.length > 40 ? '...' : ''}</span>`).join('')}
                        <div class="aio-image-button-group">
                            <button class="aio-image-copy" data-index="${i}" title="Copy Image URL">Copy</button>
                            <button class="aio-image-view" data-index="${i}" title="Open Image in New Tab">View</button>
                            <button class="aio-image-download" data-index="${i}" title="Download Image">Download</button>
                        </div>
                    </div>
                </li>`).join('')}</ul>`;
        }

        _genEmpty(type) {
            return `<div class="aio-empty">No ${type} found yet...</div>`;
        }

        _mkList(cont) {
            const list = document.createElement('ul');
            list.className = 'aio-list';
            cont.innerHTML = '';
            cont.appendChild(list);
            return list;
        }

        _clrAll() {
            if (!this._pop) {
                this._ensureUI();
                return;
            }
            console.log("AIO Scanner: Clearing all data.");

            this._cfg.linkTypes.forEach(t => {
                const c = this._pop.querySelector(`#${t}-content`);
                if (c) c.innerHTML = `<div class="aio-empty">No ${t.toUpperCase()} files detected yet...</div>`;
            });

            const sels = [this._cfg.selectors.iframe, this._cfg.selectors.network, this._cfg.selectors.image];
            const types = ['iframes', 'network requests', 'images'];
            sels.forEach((sel, i) => {
                const c = this._pop.querySelector(sel);
                if (c) c.innerHTML = this._genEmpty(types[i]);
            });

            this._urls.clear();
            this._reqs = [];

            if (this._netFltIn) this._netFltIn.value = '';
            if (this._imgFltIn) this._imgFltIn.value = '';
            this._netFiltApp = false;
        }


        /* ==========================================================================
           Utility Helpers
           ========================================================================== */

        _absUrl(u) {
            if (typeof u !== 'string') return null;
            if (u.startsWith('http:') || u.startsWith('https:') || u.startsWith('//') ||
                u.startsWith('data:') || u.startsWith('blob:') || u.startsWith('about:')) {
                return u;
            }
            try {
                if (u.startsWith('//')) {
                    return window.location.protocol + u;
                }
                return new URL(u, document.baseURI).href;
            } catch (e) {
                return null;
            }
        }

        _clnUrl(u) {
            if (typeof u !== 'string') return '';
            return u.split(/[?#]/)[0];
        }

        _truncUrl(u, maxL = this._maxUrlLen) {
            if (typeof u !== 'string' || u.length <= maxL) {
                return u || '';
            }

            try {
                const uo = new URL(u);
                const o = uo.origin;
                const p = uo.pathname;
                const s = uo.search ? '?...' : '';
                const h = uo.hash ? '#...' : '';

                const lo = o.length;
                const ls = s.length;
                const lh = h.length;
                const availP = maxL - lo - ls - lh - 5;

                if (availP <= 5) {
                    if (lo + ls + lh > maxL) {
                        return o.substring(0, maxL - 3) + '...';
                    }
                    return o + '/...' + s + h;
                }

                const segs = p.split('/').filter(Boolean);
                const fname = segs.pop() || '';

                if (fname.length >= availP) {
                    const half = Math.floor(availP / 2) - 1;
                    if (half > 0) {
                        const truncF = fname.substring(0, half) + '...' + fname.substring(fname.length - half);
                        return `${o}/.../${truncF}${s}${h}`;
                    } else {
                        return `${o}/.../...${s}${h}`;
                    }
                } else {
                    let truncP = fname;
                    let curLen = fname.length;
                    while (segs.length > 0) {
                        const next = segs.pop();
                        if (curLen + next.length + 1 <= availP) {
                            truncP = next + '/' + truncP;
                            curLen += next.length + 1;
                        } else {
                            break;
                        }
                    }
                    return `${o}/${segs.length > 0 ? '.../' : ''}${truncP}${s}${h}`;
                }
            } catch (e) {
                const half = Math.floor(maxL / 2) - 2;
                if (half <= 0) return u.substring(0, maxL - 3) + '...';
                const start = u.substring(0, half);
                const end = u.substring(u.length - half);
                return `${start}...${end}`;
            }
        }

        async _cpClip(txt) {
            if (!txt) return false;
            let ok = false;
            try {
                if (navigator.clipboard && window.isSecureContext) {
                    await navigator.clipboard.writeText(txt);
                    ok = true;
                } else {
                    ok = this._cpFb(txt);
                }
            } catch (err) {
                console.warn("AIO Scanner: Clipboard write failed, trying fallback.", err);
                ok = this._cpFb(txt);
            }
            return ok;
        }

        _cpFb(txt) {
            const ta = document.createElement('textarea');
            ta.value = txt;
            ta.style.position = 'fixed';
            ta.style.left = '-9999px';
            ta.style.top = '0px';
            ta.setAttribute('readonly', '');
            document.body.appendChild(ta);
            let ok = false;
            try {
                ta.select();
                ta.setSelectionRange(0, ta.value.length);
                ok = document.execCommand('copy');
            } catch (err) {
                console.error("AIO Scanner: Fallback copy (execCommand) failed.", err);
            }
            document.body.removeChild(ta);
            return ok;
        }

        async _cpHlp(txt, el) {
            const ok = await this._cpClip(txt);
            this._flashItm(el, ok);
        }

        _flashItm(el, ok) {
            if (!el || typeof el.style === 'undefined') return;
            const oBg = el.style.backgroundColor;
            const oBd = el.style.borderColor;
            el.style.transition = 'background-color 0.1s ease, border-color 0.1s ease';
            if (ok) {
                el.style.backgroundColor = 'rgba(50, 205, 50, 0.3)';
                el.style.borderColor = 'rgba(50, 205, 50, 0.5)';
            } else {
                el.style.backgroundColor = 'rgba(255, 0, 0, 0.3)';
                el.style.borderColor = 'rgba(255, 0, 0, 0.5)';
            }
            setTimeout(() => {
                if (el) {
                    el.style.transition = '';
                    el.style.backgroundColor = oBg;
                    el.style.borderColor = oBd;
                }
            }, 600);
        }

        _flashBtn(btn, ok, okTxt, origTxt) {
            if (!btn) return;
            const oCont = origTxt || btn.textContent;
            btn.disabled = true;
            btn.classList.remove('copied');

            if (ok) {
                btn.textContent = okTxt;
                btn.classList.add('copied');
            } else {
                btn.textContent = 'Error';
            }

            setTimeout(() => {
                if (btn) {
                    btn.textContent = oCont;
                    btn.classList.remove('copied');
                    btn.disabled = false;
                }
            }, 1500);
        }

        _dbnc(fn, wait) {
            let t;
            return (...args) => {
                clearTimeout(t);
                t = setTimeout(() => {
                    try {
                        fn.apply(this, args)
                    } catch (e) {
                        console.error("AIO Scanner: Error in debounced function:", e);
                    }
                }, wait);
            };
        }

        _monSetAttr() {
            if (window.aioSetAttrMon) return;
            try {
                const oS = Element.prototype.setAttribute;
                Element.prototype.setAttribute = function(n, v) {
                    const r = oS.apply(this, arguments);
                    if ((n === 'src' || n === 'style' || n.startsWith('data-')) &&
                        typeof v === 'string' && window.allInOneScanner) {
                        window.allInOneScanner._debScanAll();
                    }
                    return r;
                };
                window.aioSetAttrMon = true;
                console.log("AIO Scanner (Short): setAttribute monitor attached.");
            } catch (err) {
                console.error("AIO Scanner: Failed to monitor setAttribute.", err);
            }
        }

        _hdrsToObj(h) {
            const o = {};
            if (h && typeof h.forEach === 'function') {
                try {
                    h.forEach((v, k) => {
                        o[k] = v;
                    });
                } catch (e) {
                    console.warn("AIO Scanner: Error converting Headers object:", e);
                }
            }
            return o;
        }

        _parseHdrs(hStr) {
            const h = {};
            if (!hStr) return h;
            try {
                const ps = hStr.trim().split(/[\r\n]+/);
                for (const p of ps) {
                    const i = p.indexOf(':');
                    if (i > 0) {
                        const k = p.substring(0, i).trim().toLowerCase();
                        const v = p.substring(i + 1).trim();
                        if (h[k]) {
                            if (Array.isArray(h[k])) {
                                h[k].push(v);
                            } else {
                                h[k] = [h[k], v];
                            }
                        } else {
                            h[k] = v;
                        }
                    }
                }
            } catch (e) {
                console.warn("AIO Scanner: Error parsing header string:", e);
            }
            return h;
        }

        _escHtml(u) {
            if (!u || typeof u !== 'string') return String(u);
            return u.replace(/&/g, "&")
                .replace(/</g, "<")
                .replace(/>/g, ">")
                .replace(/"/g, '"')
                .replace(/'/g, "'");
        }

        async _dlImg(u, fn) {
            try {
                const r = await fetch(u, {
                    mode: 'cors',
                    credentials: 'omit'
                });
                if (!r.ok) throw new Error(`Fetch failed: ${r.status} ${r.statusText}`);
                const b = await r.blob();

                const bU = URL.createObjectURL(b);

                const a = document.createElement('a');
                a.href = bU;
                a.download = fn || 'image';
                document.body.appendChild(a);
                a.click();

                setTimeout(() => {
                    if (document.body.contains(a)) {
                        document.body.removeChild(a);
                    }
                    URL.revokeObjectURL(bU);
                }, 100);

            } catch (err) {
                console.error(`AIO Scanner: Image download failed for ${u}. Error: ${err.message}. Trying fallback.`);
                try {
                    const nt = window.open(u, '_blank');
                    if (!nt) {
                        alert(`Cannot open image (popup blocker?). Copy URL manually.\n\nURL: ${u}`);
                    }
                } catch (openErr) {
                    alert(`Failed to download or open image.\nURL: ${u}\nError: ${err.message}`);
                }
            }
        }

    } // End of AioScanner class

    // --- Initialization ---
    if (!window.aioScannerInstanceShort) {
        const init = () => {
            if (!window.aioScannerInstanceShort && document.body) {
                try {
                    console.log("AIO Scanner (Short): Initializing...");
                    window.aioScannerInstanceShort = new AioScanner();
                } catch (err) {
                    console.error("AIO Scanner (Short): CRITICAL INITIALIZATION ERROR:", err);
                }
            } else if (!document.body) {
                // Wait for body
            } else {
                // Instance exists
            }
        };

        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', init);
        } else {
            setTimeout(init, 50);
        }
    }

})();