Greasy Fork

Greasy Fork is available in English.

微博图片别裂

感恩戴德地为新浪图片添加微博Referer,支持单独访问和页面嵌入两种情况

// ==UserScript==
// @name         微博图片别裂
// @license MIT
// @namespace    http://tampermonkey.net/
// @version      2.4
// @description  感恩戴德地为新浪图片添加微博Referer,支持单独访问和页面嵌入两种情况
// @author       YourName
// @match        *://*/*
// @run-at       document-start
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @grant        unsafeWindow
// @grant        window.close
// @grant        window.onurlchange
// @connect      sinaimg.cn
// @connect      *
// ==/UserScript==

(function() {
    'use strict';

    // 配置参数
    const CONFIG = {
        TARGET_DOMAINS: ['sinaimg.cn', 'sinaimg.com'],  // 需要处理的图片域名
        REFERER_URL: 'https://weibo.com',               // 要添加的Referer
        WHITELIST_DOMAINS: ['weibo.com', 'weibo.cn'],   // 不处理的域名
        CACHE_PREFIX: 'sess_img_',                      // 缓存前缀
        MEMORY_CACHE_MAX: 50,                           // 内存缓存最大数量
        DEBUG: false                                    // 调试模式
    };

    // 调试日志
    function debugLog(...args) {
        if (CONFIG.DEBUG) {
            console.log('[RefererFix]', ...args);
        }
    }

    // 会话缓存系统
    const SessionCache = {
        // 内存缓存(快速访问)
        memoryCache: new Map(),

        // 持久化缓存键列表(当前会话)
        sessionKeys: new Set(),

        // 获取当前会话ID
        getSessionId: function() {
            let sessionId = sessionStorage.getItem('refererFixSessionId');
            if (!sessionId) {
                sessionId = 'sess_' + Date.now() + '_' + Math.random().toString(36).substr(2, 6);
                sessionStorage.setItem('refererFixSessionId', sessionId);
            }
            return sessionId;
        },

        // 生成完整缓存键
        getCacheKey: function(url) {
            return CONFIG.CACHE_PREFIX + this.getSessionId() + '_' + btoa(url).replace(/=/g, '');
        },

        // 存入缓存
        set: function(url, blobUrl) {
            const key = this.getCacheKey(url);

            // 内存缓存
            if (this.memoryCache.size >= CONFIG.MEMORY_CACHE_MAX) {
                // 移除最老的缓存项
                const oldestKey = this.memoryCache.keys().next().value;
                this.memoryCache.delete(oldestKey);
            }
            this.memoryCache.set(url, blobUrl);

            // 持久化缓存
            GM_setValue(key, {
                url: url,
                blobUrl: blobUrl,
                timestamp: Date.now()
            });
            this.sessionKeys.add(key);

            debugLog('缓存已存储:', url, '键:', key);
        },

        // 获取缓存
        get: function(url) {
            // 先检查内存缓存
            if (this.memoryCache.has(url)) {
                debugLog('从内存缓存读取:', url);
                return this.memoryCache.get(url);
            }

            // 检查持久化缓存
            const key = this.getCacheKey(url);
            const cached = GM_getValue(key);
            if (cached && cached.url === url) {
                debugLog('从持久化缓存读取:', url);

                // 存入内存缓存以便快速访问
                this.memoryCache.set(url, cached.blobUrl);
                this.sessionKeys.add(key);

                return cached.blobUrl;
            }

            return null;
        },

        // 清理当前会话缓存
        cleanup: function() {
            // 清理内存缓存
            this.memoryCache.clear();

            // 清理持久化缓存
            if (GM_listValues) {
                const allKeys = GM_listValues();
                const currentSession = this.getSessionId();

                allKeys.forEach(key => {
                    if (key.startsWith(CONFIG.CACHE_PREFIX)) {
                        // 清理不属于当前会话的缓存
                        if (!this.sessionKeys.has(key)) {
                            const sessionId = key.split('_')[2];
                            if (sessionId !== currentSession) {
                                GM_deleteValue(key);
                                debugLog('清理过期缓存:', key);
                            }
                        }
                    }
                });
            }

            debugLog('会话缓存已清理');
        },

        // 初始化会话
        init: function() {
            this.cleanup();

            // 页面卸载时清理
            window.addEventListener('beforeunload', () => {
                if (window.self === window.top) {
                    this.cleanup();
                }
            });

            // SPA应用处理
            if (typeof unsafeWindow !== 'undefined' && unsafeWindow.onurlchange === null) {
                unsafeWindow.addEventListener('urlchange', () => {
                    this.cleanup();
                });
            }
        }
    };

    // 判断是否为图片URL
    function isImageUrl(url) {
        return /\.(jpg|jpeg|png|gif|webp|bmp|svg)(\?.*)?$/i.test(url);
    }

    // 判断是否为新浪图片
    function isSinaImage(url) {
        try {
            const domain = new URL(url).hostname;
            return CONFIG.TARGET_DOMAINS.some(d => domain.includes(d));
        } catch {
            return false;
        }
    }

    // 判断是否在白名单域名
    function isWhitelistedDomain() {
        const domain = window.location.hostname;
        return CONFIG.WHITELIST_DOMAINS.some(d => domain.includes(d));
    }

    // 带Referer加载图片
    function loadImageWithReferer(url, callback) {
        // 检查缓存
        const cachedUrl = SessionCache.get(url);
        if (cachedUrl) {
            callback(cachedUrl);
            return;
        }

        debugLog('开始加载图片:', url);

        GM_xmlhttpRequest({
            method: 'GET',
            url: url,
            headers: {
                'Referer': CONFIG.REFERER_URL,
                'X-Requested-With': 'XMLHttpRequest'
            },
            responseType: 'blob',
            onload: function(response) {
                if (response.status === 200) {
                    const blobUrl = URL.createObjectURL(response.response);

                    // 存入缓存
                    SessionCache.set(url, blobUrl);

                    debugLog('图片加载成功:', url);
                    callback(blobUrl);
                } else {
                    debugLog('图片加载失败:', url, response.status);
                    callback(null);
                }
            },
            onerror: function(error) {
                debugLog('图片请求错误:', url, error);
                callback(null);
            }
        });
    }

    // 处理直接访问图片的情况
    function handleDirectImageAccess() {
        if (!isImageUrl(location.pathname) || !isSinaImage(location.href)) return;

        debugLog('检测到直接访问新浪图片:', location.href);

        // 停止原始加载
        window.stop();
        document.close();

        // 创建基础页面结构
        const basePage = `
            <!DOCTYPE html>
<html style="background:#f0f2f5;height:100%">
<head>
    <meta charset="utf-8">
    <title>图片查看器</title>
    <style>
        body, html { margin:0; padding:0; height:100%; }
        .container {
            max-width:100%;
            height:100%;
            display:flex;
            align-items:center;
            justify-content:center;
            overflow: auto;
            cursor: zoom-in;
        }
        .container.zoomed {
            align-items: flex-start;
            cursor: zoom-out;
        }
        .container.zoomed img {
            max-width: none;
            max-height: none;
            width: auto;
            height: auto;
        }
        img {
            max-width:100%;
            max-height:100%;
            margin:0 auto;
            display:block;
            box-shadow:0 2px 10px rgba(0,0,0,0.1);
            object-fit: contain;
        }
        .error {
            color: #ff4d4f;
            padding: 20px;
            text-align: center;
        }
    </style>
</head>
<body>
    <div class="container">
        <img id="refreshedImage" alt="带Referer加载的图片">
    </div>
    <script>
        // 图片点击切换缩放状态
        const container = document.querySelector('.container');
        const img = document.getElementById('refreshedImage');

        // 记录点击时的滚动位置
        let scrollPosition = { left: 0, top: 0 };

        container.addEventListener('click', function(e) {
            // 切换前保存当前滚动位置
            scrollPosition = {
                left: container.scrollLeft,
                top: container.scrollTop
            };

            // 切换缩放状态
            this.classList.toggle('zoomed');

            // 恢复之前的滚动位置
            setTimeout(() => {
                container.scrollTo(scrollPosition);
            }, 0);
        });

        // 保留油猴脚本功能
        if (window._originalGM) {
            window.GM = window._originalGM;
        }
    </script>
</body>
</html>
        `;

        // 写入基本结构
        document.open();
        document.write(basePage);
        document.close();

        // 加载图片
        const img = document.getElementById('refreshedImage');
        loadImageWithReferer(location.href, function(blobUrl) {
            if (blobUrl) {
                img.src = blobUrl;

                // 图片卸载时释放资源
                img.onload = function() {
                    URL.revokeObjectURL(blobUrl);
                };
            } else {
                const container = document.querySelector('.container');
                container.innerHTML = '<div class="error">图片加载失败,请刷新重试</div>';
            }
        });
    }

    // 处理页面中的新浪图片
    function handlePageImages() {
        if (isWhitelistedDomain()) return;

        // 处理现有图片
        document.querySelectorAll('img').forEach(img => {
            processImageElement(img);
        });

        // 监听动态添加的图片
        const observer = new MutationObserver(mutations => {
            mutations.forEach(mutation => {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeName === 'IMG') {
                        processImageElement(node);
                    } else if (node.querySelectorAll) {
                        node.querySelectorAll('img').forEach(img => {
                            processImageElement(img);
                        });
                    }
                });
            });
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    // 处理单个图片元素
    function processImageElement(img) {
        const src = img.src || img.getAttribute('data-src') || img.getAttribute('data-original');

        if (!src || !isImageUrl(src) || !isSinaImage(src)) return;

        debugLog('发现新浪图片:', src);

        // 跳过已处理的图片
        if (img.dataset.refererFixed) return;
        img.dataset.refererFixed = 'true';

        // 保存原始src
        const originalSrc = img.src;
        img.dataset.originalSrc = originalSrc;

        // 显示加载中状态
        img.style.opacity = '0.5';
        img.style.transition = 'opacity 0.3s';

        // 替换为带Referer的图片
        loadImageWithReferer(originalSrc, function(blobUrl) {
            if (blobUrl) {
                img.src = blobUrl;
                img.style.opacity = '1';

                // 图片卸载时释放资源
                img.onload = function() {
                    URL.revokeObjectURL(blobUrl);
                };
            } else {
                img.style.opacity = '1';
                img.style.filter = 'grayscale(100%)';
                img.title = '图片加载失败,原始URL: ' + originalSrc;
            }
        });
    }

    // 主函数
    function main() {
        // 初始化会话缓存
        SessionCache.init();

        // 处理直接访问图片的情况
        handleDirectImageAccess();

        // 如果不是直接访问图片,处理页面中的图片
        if (!isImageUrl(location.pathname)) {
            // 延迟执行确保DOM加载
            if (document.readyState === 'loading') {
                document.addEventListener('DOMContentLoaded', handlePageImages);
            } else {
                handlePageImages();
            }
        }
    }

    // 启动脚本
    try {
        // 保存原始GM对象(防止被覆盖)
        if (typeof unsafeWindow !== 'undefined' && !unsafeWindow._originalGM) {
            unsafeWindow._originalGM = unsafeWindow.GM;
        }

        main();
    } catch (e) {
        console.error('[RefererFix] 脚本错误:', e);
    }
})();