Universal Image Host Interceptor

Clean and fast image viewing for Fastpic, Imgbox, ImgDrive, Pixhost, ImageBam, and many more. Features: Stealth Mode (no ads/flash), Auto-Redirect (gateway bypass), and Anti-Loop.

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

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

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.

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

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         Universal Image Host Interceptor
// @namespace    https://greatest.deepsurf.us/users/108513
// @version      1.2.2
// @description  Clean and fast image viewing for Fastpic, Imgbox, ImgDrive, Pixhost, ImageBam, and many more. Features: Stealth Mode (no ads/flash), Auto-Redirect (gateway bypass), and Anti-Loop.
// @description:fr Visionneuse d'image rapide et propre pour Fastpic, Imgbox, ImgDrive, Pixhost, ImageBam et bien d'autres. Fonctionnalités : Mode Furtif (pas de pubs/flash), Redirection Auto (contournement des pages d'attente) et Anti-Boucle.
// @author       seb-du17
// @match        *://imgbox.com/*
// @match        *://*.imgbox.com/*
// @match        *://fastpic.org/view/*
// @match        *://imgxxt.in/*
// @match        *://imgdrive.net/*
// @match        *://imagebam.com/view/*
// @match        *://www.imagebam.com/image/*
// @match        *://turboimagehost.com/*
// @match        *://imagetwist.com/*
// @match        *://vipr.im/*
// @match        *://pixhost.to/*
// @match        *://*.pixhost.to/*
// @match        *://pimpandhost.com/*
// @match        *://*.imagevenue.com/*
// @match        *://imx.to/*
// @match        *://*.imx.to/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=fastpic.org
// @grant        none
// @run-at       document-start
// @compatible   firefox
// @compatible   chrome
// @license      MIT
// ==/UserScript==

/* jshint esversion:11 */

(function() {
    'use strict';

    // --- CONFIG ---
    const CONFIG = {
        SILENT_CONSOLE: true,
        DEBUG: false,

        // Watchers
        USE_MUTATION_OBSERVER: true,
        OBSERVE_ATTRIBUTES: true, // utile sur certains hosts qui changent src/content sans ajouter de nodes
        OBSERVER_MAX_RUNTIME_MS: 12000,

        // Throttle extract() calls (évite de re-parser 200 fois si Amazon-like pages)
        CHECK_MIN_INTERVAL_MS: 80, // 1 check max toutes les ~80ms

        // Fallback timer (toujours utile car certains DOM changent peu mais images/attrs oui)
        POLL_INTERVAL_MS: 250,
        POLL_MAX_ATTEMPTS: 40
    };

    // --- UTILS ---
    const noop = () => {};
    if (CONFIG.SILENT_CONSOLE) {
        window.console.log = noop;
        window.console.warn = noop;
    }
    const log = CONFIG.DEBUG ? console.log.bind(console) : noop;
		void log;
    const warn = CONFIG.DEBUG ? console.warn.bind(console) : noop;

    const HOST = window.location.hostname;
    const HREF = window.location.href;

    // --- WATCHERS CONTROL ---
    let pollerId = null;
    let observer = null;
    let observerTimeoutId = null;

    const stopPoller = () => {
        if (pollerId) {
            clearInterval(pollerId);
            pollerId = null;
        }
    };

    const stopObserver = () => {
        if (observer) {
            try { observer.disconnect(); } catch (e) {}
            observer = null;
        }
        if (observerTimeoutId) {
            clearTimeout(observerTimeoutId);
            observerTimeoutId = null;
        }
    };

    const stopAllWatchers = () => {
        stopPoller();
        stopObserver();
    };

    // --- CORE ENGINE ---
    const render = (url) => {
        if (!document.documentElement) {
            requestAnimationFrame(() => render(url));
            return;
        }

        const stealthIds = ['fp-stealth', 'imx-stealth', 'drive-stealth', 'box-stealth', 'bam-stealth'];
        stealthIds.forEach(id => {
            const el = document.getElementById(id);
            if (el) { el.remove(); }
        });

        document.documentElement.style.visibility = 'visible';
        document.documentElement.style.background = 'none';

        if (document.head) {
            while (document.head.firstChild) { document.head.removeChild(document.head.firstChild); }
        }
        if (document.body) {
            while (document.body.firstChild) { document.body.removeChild(document.body.firstChild); }
        }

        requestAnimationFrame(() => {
            if (!document.head) { document.documentElement.appendChild(document.createElement('head')); }
            if (!document.body) { document.documentElement.appendChild(document.createElement('body')); }

            document.head.innerHTML = `
                <title>Image View</title>
                <meta name="viewport" content="width=device-width, initial-scale=1">
                <style>
                    /* Scroll ON */
                    html, body { margin: 0; background: #0e0e0e; width: 100%; height: 100%; overflow: auto; }

                    /* Pas de flex-centering: on laisse le scroll naturel */
                    body { display: block; }

                    /* Taille native (pas d'upscale), mais sans “fit-to-viewport” */
                    img {
                        display: block;
                        margin: 0 auto;
                        box-shadow: 0 0 30px rgba(0,0,0,0.5);
                        max-width: 100vw;
                        max-height: none;
                        object-fit: initial;
                        width: auto;
                        height: auto;
                        cursor: default;
                    }
                </style>
            `;
            document.body.innerHTML = `<img src="${url}" alt="Full Size">`;

            // “anti-injection” minimaliste (comme ton script)
            new MutationObserver((mutations) => {
                mutations.forEach((m) => {
                    m.addedNodes.forEach((n) => {
                        if (n && (n.tagName === 'SCRIPT' || n.tagName === 'IFRAME')) { n.remove(); }
                    });
                });
            }).observe(document.documentElement, { childList: true, subtree: true });
        });
    };

    const cleanAndShow = (src, refererHost) => {
        try { window.stop(); } catch (e) {}

        src = src.replace(/\\\//g, '/');

        if (src.startsWith('//')) {
            src = window.location.protocol + src;
        }

        if (refererHost === 'fastpic') {
            render(src);
            return;
        }

        if (refererHost === 'pixhost') {
            if (src.includes('/thumbs/')) {
                const newSrc = src.replace('//t', '//img').replace('/thumbs/', '/images/');
                const imgCheck = new Image();
                imgCheck.onload = () => { render(newSrc); };
                imgCheck.onerror = () => { render(src); };
                imgCheck.src = newSrc;
                return;
            }
        } else {
            src = src.replace(/(\.|_)(md|th|tn|thumbnail|preview)(\.|_)/i, '$1$3');
        }

        render(src);
    };

    const finalize = (src, refererHost) => {
        stopAllWatchers();
        if (src) {
            cleanAndShow(src, refererHost);
        }
    };

    // --- MODULES ---
    const Modules = {
                fastpic: {
            check: () => HOST.includes('fastpic'),
            init: () => {
                if (document.documentElement) {
                    const s = document.createElement('style');
                    s.id = 'fp-stealth';
                    s.textContent = 'html, body { visibility: hidden !important; background: #0e0e0e !important; }';
                    document.documentElement.appendChild(s);
                }
            },
            extract: () => {
                // Sélecteurs directs en premier (O(1), plus rapide que boucle)
                const direct = document.querySelector('#image, #main-image, img.image-main');
                if (direct && direct.src) { return direct.src; }
                // Fallback : boucle sur tous les img, cherche /big/
                const imgs = document.getElementsByTagName('img');
                for (let i = 0; i < imgs.length; i++) {
                    if (imgs[i].src && imgs[i].src.includes('/big/')) {
                        return imgs[i].src;
                    }
                }
                return null;
            },
            onFail: () => {
                const s = document.getElementById('fp-stealth');
                if (s) { s.remove(); }
                if (document.documentElement) { document.documentElement.style.visibility = 'visible'; }
            }
        },

        pixhost: {
            check: () => HOST.includes('pixhost'),
            extract: () => {
                const scripts = document.getElementsByTagName('script');
                for (let i = 0; i < scripts.length; i++) {
                    const html = scripts[i].innerHTML;
                    if (html.includes('pswp_items')) {
                        const urls = html.match(/https?:\\?\/\\?\/[^"']+\.(jpg|jpeg|png|webp)/gi);
                        if (urls && urls.length > 0) {
                            const best = urls.find(u => !u.includes('/thumbs/') && !u.includes('/show/')) || urls[0];
                            if (!best.includes('/show/')) { return best; }
                        }
                    }
                }

                const img = document.querySelector('#image, #show_image, img.image-center');
                if (img && img.src && !img.src.includes('/show/')) { return img.src; }

                return null;
            }
        },

        imx: {
            check: () => HOST.includes('imx.to'),
            init: () => {
                if (document.documentElement) {
                    const s = document.createElement('style');
                    s.id = 'imx-stealth';
                    s.textContent = 'html, body { visibility: hidden !important; background: #0e0e0e !important; }';
                    document.documentElement.appendChild(s);
                }
            },
            extract: () => {
                const img = document.querySelector('#iimg') || document.querySelector('img.centred');
                if (img) { return img.src; }

                const urlParts = HREF.split('/');
                const id = urlParts[urlParts.length - 1];
                if (id.length > 3) {
                    const candidate = document.querySelector(`img[src*="${id}"]`);
                    if (candidate && !candidate.src.includes('logo')) { return candidate.src; }
                }

                const continueBtn = document.querySelector('input[name="imgContinue"]');
                if (continueBtn) {
                    const key = 'imx_click_' + HREF;
                    if (!sessionStorage.getItem(key)) {
                        sessionStorage.setItem(key, '1');
                        setTimeout(() => { continueBtn.click(); }, 500);
                    }
                    return null;
                }

                return null;
            },
            onFail: () => {
                const s = document.getElementById('imx-stealth');
                if (s) { s.remove(); }
                if (document.documentElement) { document.documentElement.style.visibility = 'visible'; }
            }
        },

        imagevenue: {
            check: () => HOST.includes('imagevenue.com'),
            extract: () => {
                const continueLink = document.querySelector('a[title="Continue to ImageVenue"]');
                if (continueLink && continueLink.href) {
                    if (continueLink.href !== HREF) { window.location.href = continueLink.href; }
                    return null;
                }
                const img = document.querySelector('img.card-img-top') || document.querySelector('#main-image');
                if (img) { return img.src; }

                const imgs = document.querySelectorAll('img[src*=".jpg"], img[src*=".jpeg"], img[src*=".png"]');
                for (let i = 0; i < imgs.length; i++) {
                    if (imgs[i].naturalWidth > 300 || (imgs[i].style.width && parseInt(imgs[i].style.width) > 300)) {
                        return imgs[i].src;
                    }
                }
                return null;
            }
        },

        imagetwist: {
            check: () => HOST.includes('imagetwist') || HOST.includes('vipr.im'),
            extract: () => {
                let img = document.querySelector('img.pic');
                if (img && img.src) { return img.src; }
                const urlParts = HREF.split('/');
                const filename = urlParts[urlParts.length - 1];
                if (filename.length > 5) {
                    const selector = `img[src*="${filename}"]`;
                    const candidate = document.querySelector(selector);
                    if (candidate && candidate.src !== HREF) { return candidate.src; }
                }
                return null;
            }
        },

        imgxxt: {
            check: () => HOST.includes('imgxxt'),
            extract: () => {
                const link = document.querySelector('link[rel="image_src"]');
                if (link) { return link.href; }
                const meta = document.querySelector('meta[property="og:image"]');
                if (meta) { return meta.content; }
                const v = document.querySelector('.image-viewer-container img');
                if (v) { return v.src; }
                return null;
            }
        },

        imgdrive: {
            check: () => HOST.includes('imgdrive'),
            init: () => {
                if (document.documentElement) {
                    const s = document.createElement('style');
                    s.id = 'drive-stealth';
                    s.textContent = 'html, body { visibility: hidden !important; background: #0e0e0e !important; }';
                    document.documentElement.appendChild(s);
                }
            },
            extract: () => {
                const og = document.querySelector('meta[property="og:image"]');
                if (og && og.content) {
                    const thumb = og.content;
                    if (thumb.includes('/small/')) {
                        const hd = thumb.replace('/small/', '/big/');
                        const imgTest = new Image();
                        imgTest.onload = () => { finalize(hd, null); };
                        imgTest.src = hd;
                    }
                }
                const continueLink = document.querySelector('a[onclick*="closeOverlay"]');
                if (continueLink) {
                    if (typeof unsafeWindow !== 'undefined' && unsafeWindow.closeOverlay) {
                        unsafeWindow.closeOverlay();
                    } else {
                        continueLink.click();
                    }
                    return null;
                }
                const img = document.querySelector('img.centred_resized, img.main-image, img.pic');
                if (img) { return img.src; }
                const imgId = document.querySelector('#myImage, #main_image');
                if (imgId) { return imgId.src; }
                return null;
            },
            onFail: () => {
                const s = document.getElementById('drive-stealth');
                if (s) { s.remove(); }
                if (document.documentElement) { document.documentElement.style.visibility = 'visible'; }
            }
        },

				imgbox: {
            check: () => HOST.includes('imgbox'),
            init: () => {
                if (document.documentElement) {
                    const s = document.createElement('style');
                    s.id = 'box-stealth';
                    s.textContent = 'html, body { visibility: hidden !important; background: #0e0e0e !important; }';
                    document.documentElement.appendChild(s);
                }
            },
            extract: () => {
                // URL directe vers image (avec ou sans paramètres, webp inclus)
                if (HREF.match(/\.(jpg|jpeg|png|gif|webp)(\?.*)?$/i)) { return HREF; }
                // Sélecteurs imgbox connus (id principal + fallback)
                const img = document.querySelector('#img') || document.querySelector('#image');
                if (img && img.src) { return img.src; }
                // Fallback galerie : première image full-size dans une page /g/
                if (HREF.includes('/g/')) {
                    const galleryImg = document.querySelector('img[src*="//i."][src*="imgbox"]') ||
                                       document.querySelector('.gallery-image img, .image-container img');
                    if (galleryImg && galleryImg.src) { return galleryImg.src; }
                }
                return null;
            },
            onFail: () => {
                const s = document.getElementById('box-stealth');
                if (s) { s.remove(); }
                if (document.documentElement) { document.documentElement.style.visibility = 'visible'; }
            }
        },

        imagebam: {
            check: () => HOST.includes('imagebam'),
            init: () => {
                if (document.documentElement) {
                    const s = document.createElement('style');
                    s.id = 'bam-stealth';
                    s.textContent = 'html, body { visibility: hidden !important; background: #0e0e0e !important; }';
                    document.documentElement.appendChild(s);
                }
                document.cookie = "nsfw_inter=1; path=/";
            },
            extract: () => {
                const img = document.querySelector('img.main-image');
                if (img) { return img.src; }
                const continueLink = document.querySelector('a[data-shown="inter"]');
                if (continueLink) { continueLink.click(); return null; }
                const allLinks = document.getElementsByTagName('a');
                for (let i = 0; i < allLinks.length; i++) {
                    if (allLinks[i].textContent.includes('Continue to your image')) {
                        allLinks[i].click();
                        return null;
                    }
                }
                const imgs = document.querySelectorAll('img[src*=".jpg"], img[src*=".jpeg"], img[src*=".png"]');
                for (let i = 0; i < imgs.length; i++) {
                    if (imgs[i].naturalWidth > 300 || (imgs[i].style.width && parseInt(imgs[i].style.width) > 300)) {
                        return imgs[i].src;
                    }
                }
                return null;
            },
            onFail: () => {
                const s = document.getElementById('bam-stealth');
                if (s) { s.remove(); }
                if (document.documentElement) { document.documentElement.style.visibility = 'visible'; }
            }
        },

        generic: {
            check: () => true,
            extract: () => {
                if (HOST.includes('pimpandhost')) { return document.querySelector('.main-image-wrapper')?.dataset.src; }
                return null;
            }
        }
    };

    // --- PICK MODULE ---
    let activeModule = Modules.generic;

    if (Modules.fastpic.check()) { activeModule = Modules.fastpic; }
    else if (Modules.imgdrive.check()) { activeModule = Modules.imgdrive; }
    else if (Modules.imgbox.check()) { activeModule = Modules.imgbox; }
    else if (Modules.imagebam.check()) { activeModule = Modules.imagebam; }
    else if (Modules.pixhost.check()) { activeModule = Modules.pixhost; }
    else if (Modules.imagetwist.check()) { activeModule = Modules.imagetwist; }
    else if (Modules.imgxxt.check()) { activeModule = Modules.imgxxt; }
    else if (Modules.imagevenue.check()) { activeModule = Modules.imagevenue; }
    else if (Modules.imx.check()) { activeModule = Modules.imx; }

    if (activeModule.init) { activeModule.init(); }

    const getRefererHost = () => (
        activeModule === Modules.fastpic ? 'fastpic' :
        (activeModule === Modules.pixhost ? 'pixhost' : null)
    );

    // --- Extraction orchestration (MO + fallback poll) ---
    let lastCheckTs = 0;
    let checkScheduled = false;

    const tryExtract = () => {
        const now = Date.now();
        if (now - lastCheckTs < CONFIG.CHECK_MIN_INTERVAL_MS) {
            return;
        }
        lastCheckTs = now;

        let src = null;
        try {
            src = activeModule.extract();
        } catch (e) {
            warn('[UHI] extract error', e);
            src = null;
        }

        if (src) {
            finalize(src, getRefererHost());
        }
    };

    const scheduleCheck = () => {
        if (checkScheduled) { return; }
        checkScheduled = true;
        requestAnimationFrame(() => {
            checkScheduled = false;
            tryExtract();
        });
    };

    const startObserver = () => {
        if (!CONFIG.USE_MUTATION_OBSERVER) { return; }

        const start = () => {
            if (!document.documentElement) {
                requestAnimationFrame(start);
                return;
            }

            observer = new MutationObserver(() => {
                scheduleCheck();
            });

            observer.observe(document.documentElement, {
                childList: true,
                subtree: true,
                attributes: !!CONFIG.OBSERVE_ATTRIBUTES,
                attributeFilter: CONFIG.OBSERVE_ATTRIBUTES ? ['src', 'href', 'content', 'class', 'style'] : undefined
            });

            observerTimeoutId = setTimeout(() => {
                stopObserver();
            }, CONFIG.OBSERVER_MAX_RUNTIME_MS);
        };

        start();
    };

    const startPollerFallback = () => {
        let attempts = 0;
        pollerId = setInterval(() => {
            attempts++;
            tryExtract();

            if (attempts > CONFIG.POLL_MAX_ATTEMPTS) {
                stopPoller();
                if (activeModule.onFail) { activeModule.onFail(); }
            }
        }, CONFIG.POLL_INTERVAL_MS);
    };

    // 1) Try immediately
    tryExtract();

    // 2) Start watchers if not already finalized
    startObserver();
    startPollerFallback();

})();