Greasy Fork is available in English.

Imgur Proxy

Intercepts Imgur image requests on any site and re-routes them through proxies to bypass geoblocks.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Advertisement:

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

Advertisement:

// ==UserScript==
// @name         Imgur Proxy
// @namespace    imgur-proxy
// @version      0.5
// @description  Intercepts Imgur image requests on any site and re-routes them through proxies to bypass geoblocks.
// @author       Sharknado
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @license MIT
// @connect      i.imgur.com
// @connect      imgur.com
// @connect      images.weserv.nl
// @connect      wsrv.nl
// @run-at       document-start
// ==/UserScript==

(function () {
    'use strict';

    // -------------------------------------------------------------------------
    // Proxy fetch (userscript world — has GM_xmlhttpRequest)
    // -------------------------------------------------------------------------

    const IMGUR_RE = /^https?:\/\/(?:i\.)?imgur\.com\//;

    // Tried in order; first success wins. Direct is cheapest but may be geoblocked.
    const PROXY_STRATEGIES = [
        (url) => url,
        (url) => `https://images.weserv.nl/?url=${encodeURIComponent(url)}`,
        (url) => `https://wsrv.nl/?url=${encodeURIComponent(url)}`,
    ];

    function fetchViaStrategy(strategy, originalUrl) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: strategy(originalUrl),
                responseType: 'blob',
                timeout: 10000,
                onload: (res) => {
                    if (res.status === 200 && res.response && res.response.size > 0) {
                        resolve(URL.createObjectURL(res.response));
                    } else {
                        reject(new Error(`HTTP ${res.status}`));
                    }
                },
                onerror: () => reject(new Error('network error')),
                ontimeout: () => reject(new Error('timeout')),
            });
        });
    }

    // Maps original imgur URL → Promise<blobUrl> so the same image always
    // resolves to the same blob URL and is never fetched more than once per session.
    const blobCache = new Map();

    function fetchAsBlob(originalUrl) {
        if (blobCache.has(originalUrl)) return blobCache.get(originalUrl);

        const promise = (async () => {
            let lastErr;
            for (const strategy of PROXY_STRATEGIES) {
                try {
                    return await fetchViaStrategy(strategy, originalUrl);
                } catch (e) {
                    lastErr = e;
                    console.debug(`[ImgurProxy] strategy failed for ${originalUrl}:`, e.message);
                }
            }
            throw lastErr;
        })();

        blobCache.set(originalUrl, promise);
        promise.catch(() => blobCache.delete(originalUrl)); // allow retry on total failure
        return promise;
    }

    // Listen for fetch requests from the injected page-world script, proxy them,
    // then return the blob URL via a response event.
    window.addEventListener('__imgurproxy_req__', async (e) => {
        const { id, url } = JSON.parse(e.detail);
        try {
            const blobUrl = await fetchAsBlob(url);
            window.dispatchEvent(new CustomEvent('__imgurproxy_res__', {
                detail: JSON.stringify({ id, blobUrl }),
            }));
        } catch {
            console.warn('[ImgurProxy] all strategies failed for', url);
            window.dispatchEvent(new CustomEvent('__imgurproxy_res__', {
                detail: JSON.stringify({ id, blobUrl: null }),
            }));
        }
    });

    // -------------------------------------------------------------------------
    // Page-world injection
    // Runs in the same JS world as the site's bundle, so its prototype override
    // actually intercepts new Image() / img.src assignments made by the page.
    // Cannot use GM APIs — communicates back via CustomEvents.
    // -------------------------------------------------------------------------

    const PAGE_SCRIPT = `(function () {
        const IMGUR_RE = /^https?:\\/\\/(?:i\\.)?imgur\\.com\\//;
        const _srcDesc = Object.getOwnPropertyDescriptor(HTMLImageElement.prototype, 'src');
        const _pending = new Map();
        let _id = 0;

        Object.defineProperty(HTMLImageElement.prototype, 'src', {
            get() { return _srcDesc.get.call(this); },
            set(value) {
                if (IMGUR_RE.test(value)) {
                    const id = ++_id;
                    _pending.set(id, this);
                    window.dispatchEvent(new CustomEvent('__imgurproxy_req__', {
                        detail: JSON.stringify({ id, url: value }),
                    }));
                    // Don't call original setter — prevents the browser ever requesting imgur
                } else {
                    _srcDesc.set.call(this, value);
                }
            },
            configurable: true,
        });

        window.addEventListener('__imgurproxy_res__', function (e) {
            const { id, blobUrl } = JSON.parse(e.detail);
            const img = _pending.get(id);
            if (!img) return;
            _pending.delete(id);
            if (blobUrl) {
                _srcDesc.set.call(img, blobUrl);
            }
            // blobUrl null = all strategies failed; img stays srcless rather than hitting imgur
        });
    })();`;

    const script = document.createElement('script');
    script.textContent = PAGE_SCRIPT;
    (document.head || document.documentElement).appendChild(script);
    script.remove();

    // -------------------------------------------------------------------------
    // MutationObserver — secondary coverage for setAttribute('src') and srcset
    // (setAttribute bypasses the prototype setter, but fires a DOM mutation)
    // -------------------------------------------------------------------------

    async function patchSrcset(srcset) {
        const parts = srcset.split(',').map(s => s.trim()).filter(Boolean);
        const patched = await Promise.all(parts.map(async (part) => {
            const spaceIdx = part.search(/\s/);
            const url = spaceIdx === -1 ? part : part.slice(0, spaceIdx);
            const descriptor = spaceIdx === -1 ? '' : part.slice(spaceIdx);
            if (!IMGUR_RE.test(url)) return part;
            try { return (await fetchAsBlob(url)) + descriptor; } catch { return part; }
        }));
        return patched.join(', ');
    }

    async function patchImg(img) {
        const src = img.getAttribute('src') || '';
        if (IMGUR_RE.test(src) && img.dataset.igProxied !== src) {
            img.dataset.igProxied = src;
            try { img.src = await fetchAsBlob(src); }
            catch (e) { console.warn('[ImgurProxy] srcset all strategies failed', e.message); }
        }
        const srcset = img.getAttribute('srcset') || '';
        if (srcset && img.dataset.igProxiedSrcset !== srcset && IMGUR_RE.test(srcset)) {
            img.dataset.igProxiedSrcset = srcset;
            try { img.srcset = await patchSrcset(srcset); }
            catch (e) { console.warn('[ImgurProxy] srcset failed:', e.message); }
        }
    }

    async function patchSource(source) {
        const srcset = source.getAttribute('srcset') || '';
        if (srcset && source.dataset.igProxied !== srcset && IMGUR_RE.test(srcset)) {
            source.dataset.igProxied = srcset;
            try { source.srcset = await patchSrcset(srcset); }
            catch (e) { console.warn('[ImgurProxy] source srcset failed:', e.message); }
        }
    }

    function scan(root) {
        root.querySelectorAll('img').forEach(patchImg);
        root.querySelectorAll('source').forEach(patchSource);
    }

    function onNode(node) {
        if (node.nodeType !== Node.ELEMENT_NODE) return;
        if (node.tagName === 'IMG') patchImg(node);
        else if (node.tagName === 'SOURCE') patchSource(node);
        else scan(node);
    }

    const observer = new MutationObserver((mutations) => {
        for (const m of mutations) {
            if (m.type === 'childList') m.addedNodes.forEach(onNode);
            else if (m.type === 'attributes') {
                const el = m.target;
                if (el.tagName === 'IMG') patchImg(el);
                else if (el.tagName === 'SOURCE') patchSource(el);
            }
        }
    });

    function start() {
        observer.observe(document.documentElement, {
            childList: true, subtree: true,
            attributes: true, attributeFilter: ['src', 'srcset'],
        });
        scan(document);
    }

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