// ==UserScript== // @name Universal Batoto Image Fixer // @namespace http://tampermonkey.net/ // @author Umbrella_Corporation // @version 8.0 // @description description Fixes broken Batoto images using an advanced hybrid engine that rewrites k/kXX hosts to n/nXX, performs background server probing, and applies smart prioritization. Optimized for Firefox and Chromium-based browsers with banner-safe handling, proper srcset support, smart number retention, and strong cross-browser stability. // @run-at document-start // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @connect pastebin.com // @icon https://www.google.com/s2/favicons?sz=64&domain=bato.to // @match *://*.mbdny.org/* // @match *://*.mbrtz.org/* // @match *://*.mbwbm.org/* // @match *://*.mbznp.org/* // @match *://*.mbqgu.org/* // @match *://*.mpfip.org/* // @match *://*.mpizz.org/* // @match *://*.mpmok.org/* // @match *://*.mpqom.org/* // @match *://*.mpqsc.org/* // @match *://*.mprnm.org/* // @match *://*.mpubn.org/* // @match *://*.mpujj.org/* // @match *://*.mpvim.org/* // @match *://*.mpypl.org/* // @match *://*.bato.to/* // @match *://bato.ac/* // @match *://bato.bz/* // @match *://bato.cc/* // @match *://bato.cx/* // @match *://bato.id/* // @match *://bato.pw/* // @match *://bato.sh/* // @match *://bato.si/* // @match *://bato.to/* // @match *://bato.vc/* // @match *://bato.day/* // @match *://bato.red/* // @match *://bato.run/* // @match *://bato.ing/* // @match *://batoto.in/* // @match *://batoto.tv/* // @match *://xbato.com/* // @match *://xbato.net/* // @match *://xbato.org/* // @match *://zbato.com/* // @match *://zbato.net/* // @match *://zbato.org/* // @match *://batpub.com/* // @match *://battwo.com/* // @match *://comiko.net/* // @match *://comiko.org/* // @match *://batotoo.com/* // @match *://batotwo.com/* // @match *://batread.com/* // @match *://readtoto.com/* // @match *://readtoto.net/* // @match *://readtoto.org/* // @match *://batocomic.com/* // @match *://batocomic.net/* // @match *://batocomic.org/* // @match *://mangatoto.com/* // @match *://mangatoto.net/* // @match *://mangatoto.org/* // ==/UserScript== /* ===================== Universal Batoto Image Fix - Complete Changelog ===================== v8.0 – ULTIMATE MERGED EDITION (2025-12-25): – MERGED: All features from v7.0 (Hybrid Probe) and v5.8 (Browser Optimizations). – INCLUDES: Advanced probing logic with server pattern caching. – INCLUDES: Candidate generation with smart priority levels. – INCLUDES: Background probe verification before URL switching. – INCLUDES: Failed server caching to avoid redundant attempts. – INCLUDES: Browser-specific optimizations (Firefox 15s / Chromium 2s intervals). – INCLUDES: Smart number retention from v5.8. – INCLUDES: Critical race condition fixes (listener before rewrite). – INCLUDES: Eager loading enforcement for Chromium optimization. – INCLUDES: Aggressive periodic fixer for .mb and .mp domains. – INCLUDES: Srcset rewriting with cached working servers. – INCLUDES: Complete domain coverage (80+ domain patterns). – Full compatibility with Firefox, Chromium, Edge, and mobile browsers. v7.0 – HYBRID PROBE ENGINE (2025-12-24): – MERGED: Advanced Probing Logic with server pattern caching. – NEW: Candidate generation with priority levels (0-5). – NEW: Background "Probe" verification (tests URLs before switching). – NEW: Failed server caching to avoid redundant 404/timeout attempts. – IMPROVED: Srcset rewriting now uses cached working server patterns. v6.0 – Expanded Domain Support (2025-12-24): – ADDED: Support for .mbdny, .mbrtz, .mbwbm, .mbznp, .mbqgu. – ADDED: Support for .mpfip, .mpizz, .mpmok, .mpqom, .mpqsc, .mprnm, .mpubn, .mpujj, .mpvim, .mpypl. – UPDATED: Aggressive fixer targets both .mb and .mp domain structures. v5.8 – Add support for Firefox v146 (2024-12-24). v5.7 – Firefox Optimization Release (2025-12-24): – NEW: Browser detection for Firefox vs Chromium-based. – Firefox Mode: Adaptive aggressive fixer with 15s interval (vs 2s for Chromium). – Firefox Mode: Selective eager loading only on error recovery (not initial). – Chromium Mode: Original aggressive behavior unchanged. – Maintains full compatibility with both browser engines. – No performance penalty on Chromium; significant improvement on Firefox. v5.6 – Final Merged Release (2025-12-24): – Combined features from v5.5 Beta, v5.3 Release, and v4.2 Chromium Tuned. – INCLUDES: Aggressive 'n' mirror enforcement (from v5.5). – INCLUDES: Priority Brute-Force with Smart Number Retention (from v5.3). – INCLUDES: Critical race condition fix (listener before rewrite). – INCLUDES: Eager loading enforcement for Chromium/V8 optimization. – INCLUDES: Aggressive periodic fixer for .mb domains. – ADDED: Support for new mirror domain bato.ing. – Final stable version with all features merged. v5.5 – Aggressive Periodic Fixer (2025-12-24 Beta): – Runs every 2 seconds to catch missed images. – Targets .mb domains specifically (mbtmv.org). – Fixes background-image CSS properties. v5.3.1 – Critical fix for tamperonkey (2025-12-23). v5.3 – Critical Race Condition Fix (2025-12-22): – CRITICAL: Listener now attached BEFORE src rewrite. – Original URL stored in data-btfx-orig for recovery. – Numeric version comparison for updates. – Smart number retention preserved. v5.2.1 – Changelog Rearrangement (2025-12-16). v5.2 – Updater and Version Alignment (2025-12-16): – Aligned internal version constant. – Corrected UPDATE_URL to Pastebin. v5.1 – Browser Support Documentation (2025-12-16): – Added list of unsupported browsers in description. v5.0.1 – Version Description Fix (2025-12-16). v5.0 – Final Release (2025-12-16): – Full feature parity with v4.9. v4.9 – Latest Stable Release (2025-12-15): – Full compatibility with Microsoft Edge for Android. – Full compatibility with Waterfox for Android. – Confirmed no breakage with AdGuard and uBlock Origin. – Improved resilience against content-blocking. v4.8 – Internal Testing (2025-12-15): – Testing with Waterfox and Microsoft Edge for Android. v4.7 – Core Overhaul & Unification (2025-12-15): 1. OPTIMIZED: Fixed memory leaks and recursion stack overflows. 2. ADDED: Full support for Cromite (Android). 3. MERGED: Aggressive 'n' Enforcement (v4.5) with Smart Number Retention (v4.6). 4. SAFETY: Added explicit checks for unsupported browsers. v4.6 – Smart Number Retention (2025-12-15): 1. Preserves original node numbers (e.g., 08) during swaps (Fixes mbtmv.org). 2. Expanded node search (00-10). 3. 'k' mirror promoted to High Priority. v4.5 – Aggressive 'n' Enforcement (2025-12-15): – Foundation for aggressive 'n' mirror targeting. v4.2 – Priority Brute Force (2025-12-15): – Reordered mirror search to try common servers (m, p, w, a) first. – Eager Loading: Forces 'loading="eager"' during recovery. – V8 Optimization: Direct property access for max speed. v4.1 – Chrome Opt (2025-12-15): – Removed Firefox safety wrappers. v3.9 – Internal Testing & removed firefox support (2025-12-15). v3.7 – Internal testing across multiple browser (2025-12-15). v3.6 – Unified milestone release (2025-12-15): – Incorporates final unified core architecture. – Mirror alignment cleanup. – Firefox-safe execution model. – Conditional v1.9.1 hybrid recovery logic. – Full mirror coverage. – Optional non-intrusive Pastebin updater. – Removal of always-on retry behavior. v3.0 – Major refactor experiments and hybrid architecture exploration (2025-12-15). v2.6 – Hybrid engine + updater attempts (2025-12-15). v2.3 – Stability fixes (2025-12-15). v2.0 – Hybrid concept introduced (2025-12-15). v1.9.1 – Hybrid update integrating fixes from redditor "mindlesstourist3" (2025-12-15): – Host rotation. – Retry/backoff. – Broken-image detection. v1.9 – Metadata fixes, stronger attribute handling, improved srcset support (2025-12-15). v1.8 – bato.si + mirror expansion (2025-12-15). v1.7 – Dynamic DOM handling (2025-12-15). v1.6 – Observer optimizations (2025-12-15). v1.5 – Community mirror expansion (2025-12-14). v1.4 – Critical banner fix (property-level img.src) (2025-12-13). v1.3 – MutationObserver introduced (2025-12-12). v1.2 – Attribute safety (2025-12-11). v1.1 – Minor fixes (2025-12-10). v1.0 – Initial k→n replacement (2025-12-09). ======================================================================================== */ (function () { 'use strict'; if (window.__BTFX_LOADED__) return; window.__BTFX_LOADED__ = true; // --- CONFIGURATION --- const VERSION = '8.0'; const UPDATE_URL = 'https://pastebin.com/raw/c0mBHwtH'; const IS_FIREFOX = /Firefox\/(\d+)/.test(navigator.userAgent); const IS_CHROMIUM = /Chrome\/|Chromium\/|Edg\/|OPR\//.test(navigator.userAgent); const PROBE_TIMEOUT = 5000; const MAX_ATTEMPTS = 30; const RETRY_DELAY = 1000; const AGGRESSIVE_INTERVAL = IS_FIREFOX ? 15000 : 2000; const ENABLE_EAGER_ON_INITIAL = !IS_FIREFOX; const FALLBACK_PREFIXES = ['n', 'x', 't', 's', 'w', 'm', 'c', 'u', 'k']; const FALLBACK_ROOTS = ['mbdny.org', 'mbrtz.org', 'bato.to', 'mbwbm.org', 'mbznp.org', 'mbqgu.org']; const SUBDOMAIN_RE = /^https?:\/\/([a-z]+)(\d{1,3})\.([a-z0-9\-]+)\.([a-z]{2,4})(\/.*)$/i; const HOST_RE = /(^|\/\/)([a-z])(\d+)(\.)/i; const serverCache = new Map(); const failedCache = new Set(); const processingImages = new WeakSet(); const imageStateMap = new WeakMap(); /* ---------- UTILS ---------- */ function parseSubdomain(src) { if (!src) return null; const m = src.match(SUBDOMAIN_RE); if (!m) return null; return { prefix: m[1].toLowerCase(), number: parseInt(m[2], 10), root: m[3].toLowerCase(), tld: m[4].toLowerCase(), path: m[5] }; } function isRemoteNewer(remote, local) { const a = String(remote).split('.').map(n => parseInt(n, 10) || 0); const b = String(local).split('.').map(n => parseInt(n, 10) || 0); const len = Math.max(a.length, b.length); for (let i = 0; i < len; i++) { const na = a[i] || 0; const nb = b[i] || 0; if (na > nb) return true; if (na < nb) return false; } return false; } function probeUrl(url, timeout = PROBE_TIMEOUT) { return new Promise((resolve, reject) => { const cacheKey = url.split('/').slice(0, 3).join('/'); if (failedCache.has(cacheKey)) return reject('cached-fail'); const img = new Image(); img.referrerPolicy = "no-referrer"; let timedOut = false; const t = setTimeout(() => { timedOut = true; img.src = ""; failedCache.add(cacheKey); reject('timeout'); }, timeout); img.onload = () => { if (!timedOut) { clearTimeout(t); if (img.width > 1 || img.height > 1) resolve(true); else { failedCache.add(cacheKey); reject('empty'); } } }; img.onerror = () => { if (!timedOut) { clearTimeout(t); failedCache.add(cacheKey); reject('error'); } }; img.src = url; }); } function quickFixURL(url) { if (typeof url !== 'string') return url; return url.replace(HOST_RE, function (_match, p1, p2, p3, p4) { return `${p1}n${p3}${p4}`; }); } /* ---------- CANDIDATE GENERATION ---------- */ function generateCandidates(parsed) { const candidates = []; const pathKey = parsed.path.split('/').slice(0, 3).join('/'); const add = (p, n, r, t, priority) => { const url = `https://${p}${String(n).padStart(2, '0')}.${r}.${t}${parsed.path}`; candidates.push({ url, priority }); }; const cacheKey = `${parsed.root}-${pathKey}`; if (serverCache.has(cacheKey)) { const cached = serverCache.get(cacheKey); add(cached.prefix, cached.number, cached.root, cached.tld, 0); } if (parsed.prefix === 'k') { add('n', parsed.number, parsed.root, parsed.tld, 1); add('x', parsed.number, parsed.root, parsed.tld, 1); } FALLBACK_PREFIXES.forEach(letter => { if (letter !== parsed.prefix) add(letter, parsed.number, parsed.root, parsed.tld, 2); }); [1, 2, 3, 4, 5, 0, 8, 10].forEach(i => { if (i !== parsed.number) add(parsed.prefix, i, parsed.root, parsed.tld, 3); }); FALLBACK_ROOTS.forEach(root => { const [r, t] = root.split('.'); if (r !== parsed.root) add(parsed.prefix, parsed.number, r, t, 4); }); return [...new Set(candidates.sort((a, b) => a.priority - b.priority).map(c => c.url))].slice(0, MAX_ATTEMPTS); } /* ---------- CORE ENGINES ---------- */ function rewriteSrcset(srcset, workingUrl) { if (!srcset) return null; const p = parseSubdomain(workingUrl); if (!p) return null; const newBase = `https://${p.prefix}${String(p.number).padStart(2, '0')}.${p.root}.${p.tld}`; return srcset.replace(/https?:\/\/[a-z]+\d{1,3}\.[a-z0-9\-]+\.[a-z]{2,4}/gi, newBase); } async function advancedFixImage(img, isRetry = false) { if (processingImages.has(img) || img.dataset.btfxStatus === "done") return; processingImages.add(img); img.dataset.btfxStatus = "probing"; const parsed = parseSubdomain(img.src); if (!parsed) { processingImages.delete(img); return; } const candidates = generateCandidates(parsed); let lastError = null; for (let i = 0; i < candidates.length; i++) { const url = candidates[i]; const serverPattern = url.split('/').slice(0, 3).join('/'); if (failedCache.has(serverPattern)) continue; try { await probeUrl(url, PROBE_TIMEOUT + (i > 5 ? 1000 : 0)); const successParsed = parseSubdomain(url); const pathKey = parsed.path.split('/').slice(0, 3).join('/'); serverCache.set(`${parsed.root}-${pathKey}`, successParsed); img.referrerPolicy = "no-referrer"; img.src = url; if (img.srcset) { const newSet = rewriteSrcset(img.srcset, url); if (newSet) img.srcset = newSet; } img.dataset.btfxStatus = "done"; processingImages.delete(img); return; } catch (e) { lastError = e; if (e === 'timeout' && i > 8) break; } } if (!isRetry && lastError === 'timeout') { setTimeout(() => advancedFixImage(img, true), RETRY_DELAY); } processingImages.delete(img); } function preemptiveQuickFix(img) { if (img.dataset.btfxStatus) return; const parsed = parseSubdomain(img.src); if (!parsed || parsed.prefix !== 'k') return; const newUrl = img.src.replace(SUBDOMAIN_RE, (m, p1, p2, p3, p4, p5) => `https://n${p2}.${p3}.${p4}${p5}`); img.dataset.btfxOrig = img.src; img.referrerPolicy = "no-referrer"; img.src = newUrl; if (IS_CHROMIUM && ENABLE_EAGER_ON_INITIAL) img.loading = 'eager'; img.dataset.btfxStatus = "preemptive"; setTimeout(() => { if (img.complete && img.naturalWidth === 0) advancedFixImage(img); }, 2000); } function rewrite(img) { let touched = false; const attributes = ['src', 'data-src', 'srcset']; for (const attr of attributes) { const value = img.getAttribute(attr); if (!value) continue; if (attr === 'srcset') { const newValue = value.split(',').map(p => { const parts = p.trim().split(/\s+/); const fixed = quickFixURL(parts[0]); if (fixed !== parts[0]) { parts[0] = fixed; } return parts.join(' '); }).join(', '); if (newValue !== value) { img.setAttribute(attr, newValue); touched = true; } } else { const fixed = quickFixURL(value); if (fixed !== value) { img.setAttribute(attr, fixed); touched = true; } } } if (touched) { img.referrerPolicy = 'no-referrer'; if (ENABLE_EAGER_ON_INITIAL) { img.loading = 'eager'; } } return touched; } /* ---------- DOM HANDLING ---------- */ const scan = (root = document) => { const imgs = root instanceof HTMLImageElement ? [root] : root.querySelectorAll('img'); imgs.forEach(img => { if (img.dataset.btfxStatus === "done") return; if (!img.dataset.btfxAttached) { img.dataset.btfxAttached = "1"; img.addEventListener('error', () => advancedFixImage(img), { once: true }); } preemptiveQuickFix(img); rewrite(img); if (img.complete && img.naturalWidth === 0) advancedFixImage(img); }); }; const observer = new MutationObserver(mutations => { mutations.forEach(m => { m.addedNodes.forEach(node => node.nodeType === 1 && scan(node)); if (m.type === 'attributes' && m.target.tagName === 'IMG') { if (!m.target.dataset.btfxStatus) scan(m.target); } }); }); observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['src', 'srcset', 'data-src'] }); /* ---------- AGGRESSIVE PERIODIC FIXER ---------- */ setInterval(() => { requestAnimationFrame(() => { document.querySelectorAll('img').forEach(img => { const src = img.getAttribute('src'); if (src && (src.includes('.mb') || src.includes('.mp')) && src.includes('//k')) { img.src = src.replace(/\/\/k(?=\d+)/ig, '//n'); } }); document.querySelectorAll('[style*="url"]').forEach(el => { const bg = el.style.backgroundImage; if (bg && (bg.includes('.mb') || bg.includes('.mp')) && bg.includes('//k')) { el.style.backgroundImage = bg.replace(/\/\/k(?=\d+)/ig, '//n'); } }); }); }, AGGRESSIVE_INTERVAL); /* ---------- UPDATER ---------- */ (function updater() { if (typeof GM_xmlhttpRequest === 'undefined') return; const check = (m) => { GM_xmlhttpRequest({ method: 'GET', url: UPDATE_URL, onload: (res) => { const ver = (res.responseText || '').match(/@version\s+([0-9.]+)/i); if (ver && isRemoteNewer(ver[1], VERSION)) { if (confirm(`[BTFX] Update available: v${VERSION} -> v${ver[1]}`)) window.open(UPDATE_URL, '_blank'); } else if (m) alert(`BTFX v${VERSION} is up to date. [${IS_FIREFOX ? 'Firefox' : 'Chromium'}-optimized]`); } }); }; setTimeout(check, 5000); if (typeof GM_registerMenuCommand === 'function') GM_registerMenuCommand('BTFX: Check Updates', () => check(true)); })(); if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', scan); else scan(); })();