Greasy Fork镜像 is available in English.

Input Guardian

Enhanced input preservation with security checks, performance optimizations, and user controls.

Versión del día 24/03/2025. Echa un vistazo a la versión más reciente.

// ==UserScript==
// @name         Input Guardian
// @namespace    https://spin.rip/
// @match        *://*/*
// @grant        GM_registerMenuCommand
// @version      1.0
// @author       Spinfal
// @description  Enhanced input preservation with security checks, performance optimizations, and user controls.
// @license      AGPL-3.0 License
// ==/UserScript==

(function () {
    'use strict';

    const dbName = "InputGuardianDB";
    const storeName = "inputs";
    const dbVersion = 2;
    const DEBOUNCE_TIME = 500; // ms
    const CLEANUP_DAYS = 30;
    const SENSITIVE_NAMES = /(ccnum|creditcard|cvv|ssn|sin|securitycode)/i;

    // Security: Additional sensitive field check
    const isSensitiveField = (input) => {
        return SENSITIVE_NAMES.test(input.id) ||
               SENSITIVE_NAMES.test(input.name) ||
               input.closest('[data-sensitive="true"]');
    };

    // Performance: Debounce function
    const debounce = (func, wait) => {
        let timeout;
        return (...args) => {
            clearTimeout(timeout);
            timeout = setTimeout(() => func.apply(this, args), wait);
        };
    };

    // Enhanced database with timestamp
    const openDatabase = () => {
        return new Promise((resolve, reject) => {
            const request = indexedDB.open(dbName, dbVersion);

            request.onupgradeneeded = (event) => {
                const db = event.target.result;

                // Create or upgrade object store
                let store;
                if (!db.objectStoreNames.contains(storeName)) {
                    store = db.createObjectStore(storeName, { keyPath: "id" });
                } else {
                    store = request.transaction.objectStore(storeName);
                }

                // Ensure the "timestamp" index exists
                if (!store.indexNames.contains("timestamp")) {
                    store.createIndex("timestamp", "timestamp", { unique: false });
                }
            };

            request.onsuccess = (event) => resolve(event.target.result);
            request.onerror = (event) => reject(event.target.error);
        });
    };

    // Cleanup old entries
    const cleanupOldEntries = async () => {
        try {
            const db = await openDatabase();
            const transaction = db.transaction(storeName, "readwrite");
            const store = transaction.objectStore(storeName);
            const index = store.index("timestamp");
            const threshold = Date.now() - (CLEANUP_DAYS * 86400000);

            let cursor = await new Promise((resolve, reject) => {
                const request = index.openCursor(IDBKeyRange.upperBound(threshold));
                request.onsuccess = (event) => resolve(event.target.result);
                request.onerror = (event) => reject(event.target.error);
            });

            while (cursor) {
                store.delete(cursor.primaryKey);
                cursor = await new Promise((resolve, reject) => {
                    cursor.continue();
                    cursor.onsuccess = (event) => resolve(event.target.result);
                    cursor.onerror = (event) => reject(event.target.error);
                });
            }
        } catch (error) {
            console.error("Cleanup failed:", error);
        }
    };

    const saveInput = debounce(async (id, value) => {
        try {
            const db = await openDatabase();
            const transaction = db.transaction(storeName, "readwrite");
            const store = transaction.objectStore(storeName);
            store.put({
                id,
                value,
                timestamp: Date.now()
            });
        } catch (error) {
            console.error("Error saving input:", error);
        }
    }, DEBOUNCE_TIME);

    // Handle contenteditable elements
    const handleRichText = (element) => {
        const id = element.id || element.dataset.guardianId ||
                 Math.random().toString(36).substr(2, 9);

        element.dataset.guardianId = id;
        return id;
    };

    // User controls
    const addControls = () => {
        // Add menu command
        if (typeof GM_registerMenuCommand === 'function') {
            GM_registerMenuCommand("Clear Saved Inputs", async () => {
                try {
                    const db = await openDatabase();
                    const transaction = db.transaction(storeName, "readwrite");
                    const store = transaction.objectStore(storeName);
                    await store.clear();
                    showFeedback('Cleared all saved inputs!');
                } catch (error) {
                    console.error("Clear failed:", error);
                    showFeedback('Failed to clear inputs');
                }
            });
        }

        // Add hotkey (Ctrl+Shift+Z)
        document.addEventListener('keydown', (e) => {
            if (e.ctrlKey && e.shiftKey && e.key === 'Z') {
                indexedDB.deleteDatabase(dbName);
                showFeedback('Database reset! Please reload the page.');
            }
        });
    };

    // Visual feedback
    const showFeedback = (message) => {
        const existing = document.getElementById('guardian-feedback');
        if (existing) existing.remove();

        const div = document.createElement('div');
        div.id = 'guardian-feedback';
        div.style = `
            position: fixed;
            bottom: 20px;
            right: 20px;
            padding: 10px 20px;
            background: #4CAF50;
            color: white;
            border-radius: 5px;
            z-index: 9999;
            opacity: 1;
            transition: opacity 0.5s ease-in-out;
        `;
        div.textContent = message;
        document.body.appendChild(div);

        // Fade out and remove after 2 seconds
        setTimeout(() => {
            div.style.opacity = '0';
            setTimeout(() => div.remove(), 500);
        }, 2000);
    };

    // Enhanced input detection
    const isValidInput = (input) => {
        const isHidden = input.offsetParent === null ||
                       window.getComputedStyle(input).visibility === 'hidden';

        return !isHidden &&
               input.type !== 'password' &&
               input.autocomplete !== 'off' &&
               !isSensitiveField(input);
    };

    // Modified event listener with contenteditable support
    document.addEventListener('input', (event) => {
        const target = event.target;
        let id, value;

        if (target.isContentEditable) {
            id = handleRichText(target);
            value = target.innerHTML;
        } else if (['INPUT', 'TEXTAREA', 'SELECT'].includes(target.tagName)) {
            id = target.id || target.name;
            value = target.value;
        }

        if (id && isValidInput(target)) {
            saveInput(id, value);
        }
    });

    // Initialize
    window.addEventListener('load', () => {
        restoreInputs();
        addControls();
        cleanupOldEntries();
    });

    // Modified restore function with timestamp
    const restoreInputs = async () => {
        try {
            const db = await openDatabase();
            const transaction = db.transaction(storeName, "readonly");
            const store = transaction.objectStore(storeName);
            const request = store.getAll();

            request.onsuccess = () => {
                let restoredCount = 0;
                request.result.forEach(({ id, value }) => {
                    const input = document.querySelector(`[id="${id}"], [name="${id}"]`);
                    if (input && isValidInput(input)) {
                        if (input.isContentEditable) {
                            input.innerHTML = value;
                        } else {
                            input.value = value;
                        }
                        restoredCount++;
                    }
                });
                if (restoredCount > 0) {
                    showFeedback(`Restored ${restoredCount} input${restoredCount !== 1 ? 's' : ''}!`);
                }
            };
        } catch (error) {
            console.error("Restore failed:", error);
        }
    };

})();
长期地址
遇到问题?请前往 GitHub 提 Issues。