Imgur Proxy Auto-Loader

A robust userscript that unblocks Imgur images by pre-loading them through the DuckDuckGo image proxy and replacing the original links only after a successful load.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Imgur Proxy Auto-Loader
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  A robust userscript that unblocks Imgur images by pre-loading them through the DuckDuckGo image proxy and replacing the original links only after a successful load.
// @author       Dormy
// @icon         https://i.ibb.co/4np6h8Sr/Gemini-Generated-Image-usz9ywusz9ywusz9.png
// @match        *://*/*
// @grant        none
// @license      MIT
// ==/UserScript==
 
(function() {
    'use strict';
 
    // =========================================================================
    // CONFIGURATION
    // Comma-separated list of web proxy prefixes. 
    // Currently set to only use DuckDuckGo as requested.
    // =========================================================================
    
    const PROXY_LIST_STRING = 'https://external-content.duckduckgo.com/iu/?u=';
    
    // Parse the string into a clean array
    const PROXIES = PROXY_LIST_STRING.split(',').map(p => p.trim()).filter(p => p.length > 0);
 
    if (PROXIES.length === 0) return; // Exit if no proxies are configured
 
    // Keep track of images currently being processed to avoid duplicate network requests
    const processing = new WeakSet();
    // Cache successful URLs so identical images on the page load instantly
    const successCache = new Map();
 
    // Core function: Test proxies recursively in the background
    function findWorkingProxy(originalUrl, proxyIndex, callback) {
        if (proxyIndex >= PROXIES.length) {
            console.error(`[Imgur Proxy] All proxies failed to load: ${originalUrl}`);
            return;
        }
 
        const proxyPrefix = PROXIES[proxyIndex];
        const testUrl = proxyPrefix + encodeURIComponent(originalUrl);
        
        // Create an off-screen image element to test the load
        const tempImg = new Image();
        
        tempImg.onload = function() {
            // SUCCESS! The image loaded. Pass the working URL back.
            callback(testUrl);
        };
        
        tempImg.onerror = function() {
            // FAILED! Log it and try the next proxy in the comma-separated list.
            console.warn(`[Imgur Proxy] Proxy failed (${proxyPrefix}). Trying next...`);
            findWorkingProxy(originalUrl, proxyIndex + 1, callback);
        };
 
        // Trigger the background network request
        tempImg.src = testUrl;
    }
 
    // Function to handle DOM elements
    function processImage(img) {
        let src = img.src || '';
        
        // Only target Imgur images that we haven't already started processing
        // Also ensure we aren't reprocessing an already proxied image
        if (!src.includes('imgur.com') || processing.has(img) || PROXIES.some(p => src.includes(p))) {
            return;
        }
 
        processing.add(img);
 
        // Strip responsive sizes so the browser doesn't override our proxied image
        if (img.hasAttribute('srcset')) img.removeAttribute('srcset');
        if (img.hasAttribute('sizes')) img.removeAttribute('sizes');
 
        // If we've already found a working proxy for this specific image, use it immediately
        if (successCache.has(src)) {
            img.src = successCache.get(src);
            return;
        }
 
        // Start the background testing process
        findWorkingProxy(src, 0, function(workingUrl) {
            successCache.set(src, workingUrl); // Save to cache
            
            // Only now, after the image has ACTUALLY loaded, do we replace it on the page
            img.src = workingUrl;       
        });
    }
 
    // Scan the page for images
    function scanDOM(root = document) {
        if (!root.querySelectorAll) return; // Ensure it's a valid DOM node
 
        // 1. Process standard images
        const images = root.querySelectorAll('img[src*="imgur.com"]');
        images.forEach(processImage);
 
        // 2. Handle <source> tags in <picture> elements
        const sources = root.querySelectorAll('source[srcset*="imgur.com"]');
        sources.forEach(source => {
            const parentPicture = source.closest('picture');
            if (parentPicture) {
                const img = parentPicture.querySelector('img');
                if (img) {
                    // Extract the base Imgur URL and let processImage handle it
                    let originalUrl = source.srcset.split(',')[0].trim().split(' ')[0];
                    if (originalUrl.includes('imgur.com')) {
                        img.src = originalUrl; 
                        processImage(img);
                    }
                }
            }
            // Remove the source tag so it stops interfering with the <img> tag
            source.remove(); 
        });
    }
 
    // Run immediately on page load
    scanDOM();
 
    // Observe the DOM for dynamically loaded content (e.g., Infinite scrolling)
    const observer = new MutationObserver(mutations => {
        mutations.forEach(mutation => {
            if (mutation.addedNodes.length > 0) {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        scanDOM(node);
                    }
                });
            }
        });
    });
 
    observer.observe(document.body, { childList: true, subtree: true });
 
})();