Android Anti-App Redirect

Prevents automatic redirection to apps on Android browsers

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Android Anti-App Redirect
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Prevents automatic redirection to apps on Android browsers
// @author       You
// @match        *://*/*
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';
    
    // Configuration
    const DEBUG = false; // Set to true to enable console logging
    const PROMPT_TIMEOUT = 5000; // How long to show the prompt (ms)
    
    // Helper function for logging
    function log(message) {
        if (DEBUG) {
            console.log(`[Anti-App Redirect] ${message}`);
        }
    }
    
    // Check if running on Android
    const isAndroid = /Android/i.test(navigator.userAgent);
    if (!isAndroid) {
        log("Not running on Android, script disabled");
        return;
    }
    
    log("Anti-App Redirect script active on Android");
    
    // Create UI elements for the prompt
    function createPromptUI() {
        const container = document.createElement('div');
        container.id = 'app-redirect-prompt';
        container.style.cssText = `
            position: fixed;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%);
            background-color: #333;
            color: white;
            padding: 15px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.3);
            z-index: 999999;
            font-family: Arial, sans-serif;
            max-width: 90%;
            display: none;
            text-align: center;
        `;
        
        const message = document.createElement('div');
        message.style.cssText = 'margin-bottom: 10px;';
        message.textContent = 'App redirect blocked. What would you like to do?';
        container.appendChild(message);
        
        const buttonContainer = document.createElement('div');
        buttonContainer.style.cssText = 'display: flex; justify-content: space-around;';
        
        const stayButton = document.createElement('button');
        stayButton.textContent = 'Stay in Browser';
        stayButton.style.cssText = `
            background-color: #4CAF50;
            border: none;
            color: white;
            padding: 8px 12px;
            border-radius: 4px;
            cursor: pointer;
            margin-right: 5px;
        `;
        
        const redirectButton = document.createElement('button');
        redirectButton.textContent = 'Open App';
        redirectButton.style.cssText = `
            background-color: #757575;
            border: none;
            color: white;
            padding: 8px 12px;
            border-radius: 4px;
            cursor: pointer;
            margin-left: 5px;
        `;
        
        buttonContainer.appendChild(stayButton);
        buttonContainer.appendChild(redirectButton);
        container.appendChild(buttonContainer);
        
        document.body.appendChild(container);
        
        return {
            container,
            stayButton,
            redirectButton
        };
    }
    
    // Store original functions to prevent infinite recursion
    const originalWindowOpen = window.open;
    const originalLocationAssign = window.location.assign;
    const originalLocationReplace = window.location.replace;
    const originalSetAttribute = Element.prototype.setAttribute;
    
    // Blocked schemes that typically trigger app redirects
    const blockedSchemes = [
        'intent:',
        'market:',
        'googlechrome:',
        'reddit:',
        'twitter:',
        'fb:',
        'tg:',
        'whatsapp:',
        'viber:',
        'spotify:',
        'youtube:',
        'vnd.youtube:',
        'instagram:',
        'snapchat:'
    ];
    
    // Variable to store the redirect URL if user chooses to continue
    let pendingRedirect = null;
    let activePrompt = null;
    let promptTimeout = null;
    
    // Function to show the redirect prompt
    function showRedirectPrompt(url) {
        // Don't show multiple prompts
        if (activePrompt) {
            clearTimeout(promptTimeout);
            document.body.removeChild(activePrompt.container);
        }
        
        pendingRedirect = url;
        log(`Blocked redirect to: ${url}`);
        
        // Wait for document body to be available
        if (!document.body) {
            document.addEventListener('DOMContentLoaded', () => showRedirectPrompt(url));
            return;
        }
        
        const ui = createPromptUI();
        activePrompt = ui;
        
        ui.stayButton.addEventListener('click', () => {
            pendingRedirect = null;
            document.body.removeChild(ui.container);
            activePrompt = null;
            clearTimeout(promptTimeout);
        });
        
        ui.redirectButton.addEventListener('click', () => {
            if (pendingRedirect) {
                log(`User allowed redirect to: ${pendingRedirect}`);
                document.body.removeChild(ui.container);
                activePrompt = null;
                clearTimeout(promptTimeout);
                
                // Use the original function to perform the redirect
                originalWindowOpen.call(window, pendingRedirect, '_self');
            }
        });
        
        // Show the prompt
        ui.container.style.display = 'block';
        
        // Auto-hide after timeout
        promptTimeout = setTimeout(() => {
            if (activePrompt === ui && ui.container.parentNode) {
                document.body.removeChild(ui.container);
                activePrompt = null;
                pendingRedirect = null;
            }
        }, PROMPT_TIMEOUT);
    }
    
    // Helper function to check if a URL should be blocked
    function shouldBlockUrl(url) {
        if (!url || typeof url !== 'string') return false;
        
        // Check for app schemes
        return blockedSchemes.some(scheme => url.toLowerCase().startsWith(scheme));
    }
    
    // Override window.open
    window.open = function(url, target, features) {
        if (shouldBlockUrl(url)) {
            showRedirectPrompt(url);
            return null;
        }
        return originalWindowOpen.call(this, url, target, features);
    };
    
    // Override location.assign
    window.location.assign = function(url) {
        if (shouldBlockUrl(url)) {
            showRedirectPrompt(url);
            return;
        }
        return originalLocationAssign.call(this, url);
    };
    
    // Override location.replace
    window.location.replace = function(url) {
        if (shouldBlockUrl(url)) {
            showRedirectPrompt(url);
            return;
        }
        return originalLocationReplace.call(this, url);
    };
    
    // Override direct location changes
    Object.defineProperty(window, 'location', {
        set: function(url) {
            if (shouldBlockUrl(url)) {
                showRedirectPrompt(url);
                return;
            }
            window.location.href = url;
        },
        get: function() {
            return document.location;
        }
    });
    
    // Intercept intent:// links
    Element.prototype.setAttribute = function(name, value) {
        if (name === 'href' && shouldBlockUrl(value)) {
            // Store the original URL
            const originalUrl = value;
            
            // Replace it with a javascript: URL that will trigger our handler
            originalSetAttribute.call(this, 'href', 'javascript:void(0)');
            originalSetAttribute.call(this, 'data-original-url', originalUrl);
            
            // Add click handler
            this.addEventListener('click', function(e) {
                e.preventDefault();
                e.stopPropagation();
                showRedirectPrompt(originalUrl);
                return false;
            });
            
            return;
        }
        return originalSetAttribute.call(this, name, value);
    };
    
    // Handle click events on links that might have been added before our script ran
    document.addEventListener('click', function(e) {
        const target = e.target.closest('a');
        if (!target) return;
        
        const href = target.getAttribute('href');
        if (shouldBlockUrl(href)) {
            e.preventDefault();
            e.stopPropagation();
            showRedirectPrompt(href);
            return false;
        }
    }, true); // Use capture phase to intercept events before they reach the link
    
    // MutationObserver to watch for dynamically added links
    const observer = new MutationObserver(function(mutations) {
        mutations.forEach(function(mutation) {
            if (mutation.type === 'attributes' && 
                mutation.attributeName === 'href' && 
                mutation.target.tagName === 'A') {
                
                const href = mutation.target.getAttribute('href');
                if (shouldBlockUrl(href)) {
                    log(`Found dynamic app link: ${href}`);
                    
                    // Replace with safe version
                    const originalUrl = href;
                    mutation.target.setAttribute('href', 'javascript:void(0)');
                    mutation.target.setAttribute('data-original-url', originalUrl);
                    
                    mutation.target.addEventListener('click', function(e) {
                        e.preventDefault();
                        e.stopPropagation();
                        showRedirectPrompt(originalUrl);
                        return false;
                    });
                }
            }
        });
    });
    
    // Start observing when the DOM is ready
    document.addEventListener('DOMContentLoaded', function() {
        observer.observe(document.body, {
            attributes: true,
            attributeFilter: ['href'],
            subtree: true
        });
        
        log("MutationObserver started");
    });
    
    // Add additional schemes to block
    function addBlockedScheme(scheme) {
        if (!scheme.endsWith(':')) {
            scheme += ':';
        }
        if (!blockedSchemes.includes(scheme)) {
            blockedSchemes.push(scheme);
            log(`Added scheme to blocklist: ${scheme}`);
        }
    }
    
    // Expose function to add schemes (can be called from console)
    window.addAppRedirectScheme = addBlockedScheme;
    
    log("Anti-App Redirect initialized successfully");
})();