Imgur Proxy

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

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

Advertisement:

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

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