- // ==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);
- }
- }
-
- })();