// ==UserScript== // @name Universal Batoto Image Fix // @namespace Umbrella_Corporation // @version 4.2.1 (Redirected to the main branch) // @description Fixes Batoto images. Tuned for Chrome/Edge/Brave. Features: Priority Brute-Force, V8-optimized State, and Attribute Monitoring. // @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 // @updateURL https://pastebin.com/raw/c0mBHwtH // @downloadURL https://pastebin.com/raw/c0mBHwtH // @match *://ato.to/* // @match *://dto.to/* // @match *://fto.to/* // @match *://hto.to/* // @match *://jto.to/* // @match *://lto.to/* // @match *://mto.to/* // @match *://nto.to/* // @match *://vto.to/* // @match *://wto.to/* // @match *://xto.to/* // @match *://yto.to/* // @match *://vba.to/* // @match *://wba.to/* // @match *://xba.to/* // @match *://yba.to/* // @match *://zba.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 *://batoto.in/* // @match *://batoto.tv/* // @match *://batotoo.com/* // @match *://batotwo.com/* // @match *://batpub.com/* // @match *://batread.com/* // @match *://battwo.com/* // @match *://xbato.com/* // @match *://xbato.net/* // @match *://xbato.org/* // @match *://zbato.com/* // @match *://zbato.net/* // @match *://zbato.org/* // @match *://comiko.net/* // @match *://comiko.org/* // @match *://mangatoto.com/* // @match *://mangatoto.net/* // @match *://mangatoto.org/* // @match *://batocomic.com/* // @match *://batocomic.net/* // @match *://batocomic.org/* // @match *://readtoto.com/* // @match *://readtoto.net/* // @match *://readtoto.org/* // @match *://kuku.to/* // @match *://okok.to/* // @match *://ruru.to/* // @match *://xdxd.to/* // ==/UserScript== /* ===================== Changelog ===================== v4.2.1 - Redirected to the main branch https://pastebin.com/c0mBHwtH for cross browser support v4.2 (Chromium Tuned) – Priority Brute Force: Reordered mirror search to try common servers (m, p, w, a) first. Eager Loading: Forces 'loading="eager"' during recovery to bypass Chrome's lazy-load during fixes. V8 Optimization: Direct property access for max speed. v4.1 (Chrome Opt) – Removed Firefox safety wrappers. ==================================================== */ (function () { 'use strict'; if (window.__BTFX_LOADED__) return; window.__BTFX_LOADED__ = true; const VERSION = '4.2'; const UPDATE_URL = 'https://pastebin.com/raw/c0mBHwtH'; // REGEX: Matches "//k01.", "//n03.", etc. const HOST_RE = /(^|\/\/)([a-z])(\d+)(\.)/i; // PRIORITY LIST (Chromium Optimized) // Instead of a-z, we check the most likely active mirrors first. const PRIORITY_LETTERS = ['m', 'p', 'w', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'l', 'o', 'q', 'r', 's', 't', 'u', 'v', 'x', 'y', 'z']; const PRIORITY_NUMBERS = ['01', '03', '00', '02', '04', '05']; /* ---------------- Pastebin updater ---------------- */ (function updater() { let checked = false; async function checkUpdate(manual) { if (checked && !manual) return; checked = true; try { const r = await fetch(UPDATE_URL, {cache: 'no-store'}); const txt = await r.text(); const m = txt.match(/@version\s+([0-9.]+)/i); if (m && parseFloat(m[1]) > parseFloat(VERSION)) { if (manual || confirm(`Update available: v${VERSION} -> v${m[1]}`)) window.open(UPDATE_URL, '_blank'); } else if (manual) alert(`Up to date (v${VERSION})`); } catch (e) {} } setTimeout(() => checkUpdate(false), 2000); if (typeof GM_registerMenuCommand === 'function') GM_registerMenuCommand('Check Updates', () => checkUpdate(true)); })(); /* ---------------- Unified Core ---------------- */ /** * Fast Rewrite: * Immediately swaps 'k' mirrors to 'n' mirrors. */ function quickFixURL(u) { if (typeof u === 'string' && /(^|\/\/)k(\d+\.)/i.test(u)) { return u.replace(/(^|\/\/)k(\d+\.)/i, '$1n$2'); } return null; } function rewrite(img) { let touched = false; const attributes = ['src', 'data-src', 'srcset']; attributes.forEach(attr => { const value = img.getAttribute(attr); if (!value) return; if (attr === 'srcset') { const newValue = value.split(',').map(p => { const parts = p.trim().split(/\s+/); const fixed = quickFixURL(parts[0]); if (fixed) parts[0] = fixed; return parts.join(' '); }).join(', '); if (newValue !== value) { img.setAttribute(attr, newValue); touched = true; } } else { const fixed = quickFixURL(value); if (fixed && fixed !== value) { img.setAttribute(attr, fixed); touched = true; } } }); if (touched) { img.referrerPolicy = 'no-referrer'; // In Chromium, if we change src, we might want to force eager loading // to ensure the new URL is fetched immediately. img.loading = 'eager'; } return touched; } /* ---------------- Recovery Logic (V8 Optimized) ---------------- */ function recover(event) { const img = event.currentTarget; // DIRECT DOM STATE (V8 Fast Property Access) if (!img.btfx) { img.btfx = { stage: 'init', lIdx: 0, nIdx: 0, orig: img.src }; } const state = img.btfx; // Safety: Stop if exhausted if (state.stage === 'failed') return; let nextUrl = null; // --- PHASE 1: Standard Swap (Ensure 'n' was tried) --- if (state.stage === 'init') { state.stage = 'bruteforce'; const simpleFix = quickFixURL(state.orig); if (simpleFix && simpleFix !== img.src) { nextUrl = simpleFix; } } // --- PHASE 2: Priority Brute Force --- if (!nextUrl) { // Check limits if (state.lIdx >= PRIORITY_LETTERS.length) { state.stage = 'failed'; console.warn('[BTFX] All mirrors failed for:', state.orig); return; } const letter = PRIORITY_LETTERS[state.lIdx]; const number = PRIORITY_NUMBERS[state.nIdx]; // Replace $2 (Letter) and $3 (Number) in the host if (HOST_RE.test(state.orig)) { const replacement = `$1${letter}${number}$4`; nextUrl = state.orig.replace(HOST_RE, replacement); } else { state.stage = 'failed'; return; } // Advance counters state.nIdx++; if (state.nIdx >= PRIORITY_NUMBERS.length) { state.nIdx = 0; state.lIdx++; } } // Apply the new URL if (nextUrl && nextUrl !== img.src) { console.log(`[BTFX] Trying mirror: ${nextUrl}`); const newOnLoad = () => { cleanup(); }; const newOnError = (e) => { cleanup(); recover(e); }; const cleanup = () => { img.removeEventListener('load', newOnLoad); img.removeEventListener('error', newOnError); }; img.addEventListener('load', newOnLoad); img.addEventListener('error', newOnError); // Force browser to fetch immediately, bypassing lazy load img.loading = 'eager'; img.referrerPolicy = 'no-referrer'; img.src = nextUrl; } } /* ---------------- Pipeline ---------------- */ function processImage(img) { // 1. Try initial Rewrite rewrite(img); // 2. Attach Error Listener if (!img.dataset.btfxAttached) { img.dataset.btfxAttached = "1"; img.addEventListener('error', recover); // Check if image is already dead (Chrome/Edge optimization) if (img.complete && img.naturalWidth === 0) { recover({ currentTarget: img }); } } } function scan(root = document) { if (root instanceof HTMLImageElement) { processImage(root); } else if (root.querySelectorAll) { root.querySelectorAll('img').forEach(processImage); } } // Initialization scan(); document.addEventListener('DOMContentLoaded', () => scan()); window.addEventListener('load', () => scan()); // Attribute Monitor (Chrome MutationObserver is very fast) const mo = new MutationObserver(mutations => { for (const mutation of mutations) { if (mutation.type === 'childList') { for (const node of mutation.addedNodes) { if (node.nodeType === 1) scan(node); } } else if (mutation.type === 'attributes') { if (mutation.target.tagName === 'IMG') { processImage(mutation.target); } } } }); mo.observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['src', 'data-src', 'srcset'] }); })();