Greasy Fork

来自缓存

Greasy Fork is available in English.

通用自动填写助手

通用自动填写助手:录制表单填写规则,实现一键自动填写。支持多语言、悬浮按钮、批量设置规则和唯一性识别模式。

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         通用自动填写助手
// @namespace     http://tampermonkey.net/
// @version       1.0
// @description    通用自动填写助手:录制表单填写规则,实现一键自动填写。支持多语言、悬浮按钮、批量设置规则和唯一性识别模式。
// @description:en  Universal Auto Fill Assistant: Record form filling rules to achieve one-click auto fill. Supports multiple languages, floating button, batch rule setting and unique recognition mode.
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_notification
// @author       QqMorning
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';
        // 状态变量
    let isRecording = false;
    let isSelecting = false;
    let currentRules = {};
    let menuCommands = [];
    let uniqueMode = true; // 默认开启唯一性识别
    let pageMatchMode = 'strict'; // 页面匹配模式

        // 配置
        const CONFIG = {
            panelPosition: 'right',
            highlightColor: '#3498db',
            recordedColor: '#2ecc71',
            language: 'zh-CN', // 默认语言
            pageMatchMode: 'strict' // strict: 严格模式, loose: 宽松模式
        };

        // 语言资源
        const i18n = {
            'zh-CN': {
                title: '自动填写助手',
                ready: '准备就绪',
                startRecording: '开始录制',
                stopRecording: '停止录制',
                autoFill: '自动填写',
                manageRules: '管理规则',
                clearRules: '清除规则',
                hideRules: '隐藏规则',
                currentRules: '当前规则',
                noRules: '暂无规则',
                recordingMode: '录制模式已开启,点击要自动填写的表单元素',
                recordingStopped: '录制已停止,规则已保存',
                clickToRecord: '点击要录制的表单元素',
                enterValue: '输入值并确认',
                fillSuccess: '成功填写 ${count} 个字段!',
                fillWarning: '没有找到匹配的字段进行填写',
                noRulesWarning: '当前页面没有保存的填写规则',
                clearConfirm: '确定要清除当前页面的所有填写规则吗?',
                clearedSuccess: '已清除当前页面的所有规则',
                uniqueMode: '唯一模式',
                uniqueModeOn: '开启',
                uniqueModeOff: '关闭',
                uniqueModeNotification: '唯一性识别模式 ${status}',
                batchFill: '批量填写',
                uniqueFill: '唯一填写',
                pageMatchMode: '页面匹配',
                strictMode: '严格',
                looseMode: '宽松',
                pageMatchModeNotification: '页面匹配模式已切换为 ${mode} 模式',
                strictModeDesc: '严格模式:区分所有URL',
                looseModeDesc: '宽松模式:忽略URL中的变化部分'
            },
            'en': {
                title: 'Auto Fill Assistant',
                ready: 'Ready',
                startRecording: 'Start Recording',
                stopRecording: 'Stop Recording',
                autoFill: 'Auto Fill',
                manageRules: 'Manage Rules',
                clearRules: 'Clear Rules',
                hideRules: 'Hide Rules',
                currentRules: 'Current Rules',
                noRules: 'No rules yet',
                recordingMode: 'Recording mode enabled, click form elements to record',
                recordingStopped: 'Recording stopped, rules saved',
                clickToRecord: 'Click form elements to record',
                enterValue: 'Enter value and confirm',
                fillSuccess: 'Successfully filled ${count} fields!',
                fillWarning: 'No matching fields found to fill',
                noRulesWarning: 'No saved fill rules for current page',
                clearConfirm: 'Are you sure you want to clear all rules for this page?',
                clearedSuccess: 'All rules for current page cleared',
                uniqueMode: 'Unique Mode',
                uniqueModeOn: 'ON',
                uniqueModeOff: 'OFF',
                uniqueModeNotification: 'Unique mode ${status}',
                batchFill: 'Batch Fill',
                uniqueFill: 'Unique Fill',
                pageMatchMode: 'Page Match',
                strictMode: 'Strict',
                looseMode: 'Loose',
                pageMatchModeNotification: 'Page match mode switched to ${mode}',
                strictModeDesc: 'Strict mode: Distinguish all URLs',
                looseModeDesc: 'Loose mode: Ignore variable parts in URLs'
            }
        };

    // 切换唯一性模式
    function toggleUniqueMode() {
        uniqueMode = !uniqueMode;
        GM_setValue('autoFillUniqueMode', uniqueMode);

        const uniqueBtn = document.getElementById('af-unique-mode');
        if (uniqueBtn) {
            uniqueBtn.textContent = `${t('uniqueMode')}: ${t(uniqueMode ? 'uniqueModeOn' : 'uniqueModeOff')}`;
            uniqueBtn.classList.toggle('af-btn-active', uniqueMode);
        }

        showNotification(t('uniqueModeNotification', { status: t(uniqueMode ? 'uniqueModeOn' : 'uniqueModeOff') }));
    }

    // 加载唯一性模式设置
    function loadUniqueMode() {
        uniqueMode = GM_getValue('autoFillUniqueMode', true);
    }

    // 加载页面匹配模式设置
    function loadPageMatchMode() {
        pageMatchMode = GM_getValue('autoFillPageMatchMode', 'strict');
    }

    // 切换页面匹配模式
    function togglePageMatchMode() {
        pageMatchMode = pageMatchMode === 'strict' ? 'loose' : 'strict';
        GM_setValue('autoFillPageMatchMode', pageMatchMode);

        const modeBtn = document.getElementById('af-page-match-mode');
        if (modeBtn) {
            modeBtn.textContent = `${t('pageMatchMode')}: ${t(pageMatchMode === 'strict' ? 'strictMode' : 'looseMode')}`;
            modeBtn.classList.toggle('af-btn-active', pageMatchMode === 'loose');
        }

        showNotification(t('pageMatchModeNotification', { mode: t(pageMatchMode === 'strict' ? 'strictMode' : 'looseMode') }));
    }


    // 获取当前语言
    function getCurrentLanguage() {
        return GM_getValue('autoFillLanguage', CONFIG.language);
    }

    // 设置语言
    function setLanguage(lang) {
        GM_setValue('autoFillLanguage', lang);
        CONFIG.language = lang;
        // 重新创建面板以应用新语言
        createControlPanel();
    }

    // 翻译函数
    function t(key, params = {}) {
        const lang = getCurrentLanguage();
        let text = i18n[lang]?.[key] || i18n['zh-CN'][key] || key;

        // 替换参数
        Object.keys(params).forEach(param => {
            text = text.replace(`\${${param}}`, params[param]);
        });

        return text;
    }


    // 创建控制面板
    function createControlPanel() {
        // 如果已存在面板,先移除
        const existingPanel = document.getElementById('auto-fill-panel');
        if (existingPanel) {
            existingPanel.remove();
        }

        const panel = document.createElement('div');
        panel.id = 'auto-fill-panel';

        const positionStyle = CONFIG.panelPosition === 'right' ?
              'right: 20px; left: auto;' : 'left: 20px; right: auto;';

        panel.innerHTML = `
            <div class="af-panel" style="${positionStyle}">
                <div class="af-header">
                    <h3>${t('title')}</h3>
                    <div class="af-language-switcher">
                        <button class="af-lang-btn ${getCurrentLanguage() === 'zh-CN' ? 'active' : ''}" data-lang="zh-CN">中</button>
                        <button class="af-lang-btn ${getCurrentLanguage() === 'en' ? 'active' : ''}" data-lang="en">EN</button>
                    </div>
                    <button class="af-close" id="af-close">×</button>
                </div>
                <div class="af-body">
                    <div class="af-status" id="af-status">
                        <span class="af-status-indicator"></span>
                        <span class="af-status-text" id="af-status-text">${t('ready')}</span>
                    </div>
                    <div class="af-buttons">
                        <button class="af-btn" id="af-start-recording">${t('startRecording')}</button>
                        <button class="af-btn af-btn-stop" id="af-stop-recording" style="display:none;">${t('stopRecording')}</button>
                        <button class="af-btn af-btn-fill" id="af-auto-fill">${t('autoFill')}</button>
                        <button class="af-btn af-btn-manage" id="af-manage-rules">${t('manageRules')}</button>
                        <button class="af-btn af-btn-clear" id="af-clear-rules">${t('clearRules')}</button>
                        <button class="af-btn af-btn-mode" id="af-page-match-mode">${t('pageMatchMode')}: ${t(pageMatchMode === 'strict' ? 'strictMode' : 'looseMode')}</button>
                    </div>
                    <div class="af-rules-list" id="af-rules-list" style="display:none;">
                        <h4>${t('currentRules')}:</h4>
                        <ul id="af-rules-container"></ul>
                    </div>
                </div>
            </div>
        `;

        document.body.appendChild(panel);
        setupEventListeners();
        loadRulesForPage();
        updateRulesList();

        // 添加面板样式
        addPanelStyles();
    }
    // 添加面板样式
    function addPanelStyles() {
        // 如果样式已存在,先移除
        const existingStyle = document.getElementById('auto-fill-styles');
        if (existingStyle) {
            existingStyle.remove();
        }

        const style = document.createElement('style');
        style.id = 'auto-fill-styles';
       style.textContent = `
    #auto-fill-panel {
        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        font-size: 13px;
    }
    .af-panel {
        position: fixed;
        top: 50px;
        background: white;
        padding: 0;
        border-radius: 6px;
        box-shadow: 0 2px 12px rgba(0,0,0,0.15);
        z-index: 10000;
        width: 220px;
        overflow: hidden;
        border: 1px solid #e0e0e0;
    }
    .af-header {
        background: #3498db;
        color: white;
        padding: 8px 12px;
        display: flex;
        justify-content: space-between;
        align-items: center;
    }
    .af-header h3 {
        margin: 0;
        font-size: 14px;
        font-weight: 600;
        color: white;
    }
    .af-close {
        background: none;
        border: none;
        color: white;
        font-size: 16px;
        cursor: pointer;
        line-height: 1;
        padding: 0;
        width: 20px;
        height: 20px;
        display: flex;
        align-items: center;
        justify-content: center;
    }
    .af-body {
        padding: 12px;
    }
    .af-status {
        display: flex;
        align-items: center;
        margin-bottom: 12px;
        padding: 6px 10px;
        background: #f8f9fa;
        border-radius: 3px;
        font-size: 12px;
    }
    .af-status-indicator {
        width: 8px;
        height: 8px;
        border-radius: 50%;
        background: #6c757d;
        margin-right: 6px;
    }
    .af-status.recording .af-status-indicator {
        background: #e74c3c;
        animation: af-pulse 1.5s infinite;
    }
    .af-status.selecting .af-status-indicator {
        background: #f39c12;
        animation: af-pulse 1.5s infinite;
    }
    @keyframes af-pulse {
        0% { opacity: 1; }
        50% { opacity: 0.5; }
        100% { opacity: 1; }
    }
    .af-buttons {
        display: flex;
        flex-direction: column;
        gap: 6px;
    }
    .af-btn {
        padding: 7px 10px;
        border: none;
        border-radius: 3px;
        background: #3498db;
        color: white;
        cursor: pointer;
        font-size: 12px;
        transition: background 0.2s;
    }
    .af-btn:hover {
        background: #2980b9;
    }
    .af-btn-stop {
        background: #e74c3c;
    }
    .af-btn-stop:hover {
        background: #c0392b;
    }
    .af-btn-fill {
        background: #2ecc71;
    }
    .af-btn-fill:hover {
        background: #27ae60;
    }
    .af-btn-manage {
        background: #9b59b6;
    }
    .af-btn-manage:hover {
        background: #8e44ad;
    }
    .af-btn-clear {
        background: #e67e22;
    }
    .af-btn-clear:hover {
        background: #d35400;
    }
    .af-rules-list {
        margin-top: 12px;
        border-top: 1px solid #e0e0e0;
        padding-top: 12px;
    }
    .af-rules-list h4 {
        margin: 0 0 8px 0;
        font-size: 13px;
        color: #555;
    }
    .af-rules-list ul {
        margin: 0;
        padding: 0;
        list-style: none;
        max-height: 150px;
        overflow-y: auto;
        font-size: 11px;
    }
    .af-rules-list li {
        padding: 3px 0;
        border-bottom: 1px solid #f0f0f0;
        display: flex;
        justify-content: space-between;
    }
    .af-rule-value {
        color: #777;
        font-style: italic;
        max-width: 100px;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
    .af-highlight {
        outline: 2px solid ${CONFIG.highlightColor} !important;
        background-color: rgba(52, 152, 219, 0.1) !important;
    }
    .af-recorded {
        outline: 2px solid ${CONFIG.recordedColor} !important;
        background-color: rgba(46, 204, 113, 0.1) !important;
    }
    .af-btn-active {
        background: #e67e22 !important;
    }
    .af-btn-active:hover {
        background: #d35400 !important;
    }
    .af-language-switcher {
        display: flex;
        gap: 4px;
    }
    .af-lang-btn {
        background: rgba(255,255,255,0.2);
        border: 1px solid rgba(255,255,255,0.3);
        color: white;
        padding: 1px 4px;
        border-radius: 2px;
        cursor: pointer;
        font-size: 10px;
    }
    .af-lang-btn.active {
        background: rgba(255,255,255,0.4);
        font-weight: bold;
    }
     .af-btn-mode {
        background: #16a085;
    }
    .af-btn-mode:hover {
        background: #1abc9c;
    }
`;
        document.head.appendChild(style);
    }

    // 创建悬浮按钮
    function createFloatingButton() {
        const existingBtn = document.getElementById('af-floating-btn');
        if (existingBtn) return;

        const floatBtn = document.createElement('div');
        floatBtn.id = 'af-floating-btn';
        floatBtn.innerHTML = '🔧';
        floatBtn.title = t('title');

        document.body.appendChild(floatBtn);

        // 添加悬浮按钮样式
        addFloatingButtonStyles();

        // 事件监听
        floatBtn.addEventListener('click', togglePanel);
    }

    // 添加悬浮按钮样式
    function addFloatingButtonStyles() {
        const style = document.createElement('style');
        style.id = 'af-floating-styles';
        style.textContent = `
            #af-floating-btn {
                position: fixed;
                bottom: 20px;
                right: 20px;
                width: 50px;
                height: 50px;
                background: #3498db;
                color: white;
                border-radius: 50%;
                display: flex;
                align-items: center;
                justify-content: center;
                font-size: 20px;
                cursor: pointer;
                z-index: 9999;
                box-shadow: 0 4px 12px rgba(0,0,0,0.3);
                transition: all 0.3s ease;
                user-select: none;
                opacity: 0.2;
            }
            #af-floating-btn:hover {
                background: #2980b9;
                transform: scale(1.1);
                opacity: 0.6;
            }
            .af-language-switcher {
                display: flex;
                gap: 5px;
            }
            .af-lang-btn {
                background: rgba(255,255,255,0.2);
                border: 1px solid rgba(255,255,255,0.3);
                color: white;
                padding: 2px 6px;
                border-radius: 3px;
                cursor: pointer;
                font-size: 12px;
            }
            .af-lang-btn.active {
                background: rgba(255,255,255,0.4);
                font-weight: bold;
            }
        `;
        document.head.appendChild(style);
    }

    // 切换面板显示/隐藏
    function togglePanel() {
        const panel = document.getElementById('auto-fill-panel');
        if (panel) {
            hidePanel();
        } else {
            createControlPanel();
        }
    }

    // 设置事件监听器
    function setupEventListeners() {
        const pageMatchBtn = document.getElementById('af-page-match-mode');
        if (pageMatchBtn) pageMatchBtn.addEventListener('click', togglePageMatchMode);

        const uniqueBtn = document.getElementById('af-unique-mode');
        if (uniqueBtn) uniqueBtn.addEventListener('click', toggleUniqueMode);
        // 添加语言切换按钮事件
        const langButtons = document.querySelectorAll('.af-lang-btn');
        langButtons.forEach(btn => {
            btn.addEventListener('click', (e) => {
                const lang = e.target.dataset.lang;
                setLanguage(lang);
            });
        });

        // 使用更安全的方法添加事件监听器
        const startBtn = document.getElementById('af-start-recording');
        const stopBtn = document.getElementById('af-stop-recording');
        const fillBtn = document.getElementById('af-auto-fill');
        const manageBtn = document.getElementById('af-manage-rules');
        const clearBtn = document.getElementById('af-clear-rules');
        const closeBtn = document.getElementById('af-close');

        if (startBtn) startBtn.addEventListener('click', startRecording);
        if (stopBtn) stopBtn.addEventListener('click', stopRecording);
        if (fillBtn) fillBtn.addEventListener('click', autoFill);
        if (manageBtn) manageBtn.addEventListener('click', toggleRulesList);
        if (clearBtn) clearBtn.addEventListener('click', clearRules);
        if (closeBtn) closeBtn.addEventListener('click', hidePanel);

        // 添加全局键盘快捷键
        document.addEventListener('keydown', handleKeydown);
    }

    // 处理键盘快捷键
    function handleKeydown(e) {
        // Shift+R 开始/停止录制
        if (e.shiftKey && e.key === 'R') {
            e.preventDefault();
            if (isRecording) {
                stopRecording();
            } else {
                startRecording();
            }
        }

        // Shift+W 自动填写
        if (e.shiftKey && e.key === 'W') {
            e.preventDefault();
            autoFill();
        }

        // Esc 停止录制或隐藏面板
        if (e.key === 'Escape') {
            if (isRecording) {
                stopRecording();
            } else {
                hidePanel();
            }
        }
    }
    // 开始录制
    function startRecording() {
        isRecording = true;
        isSelecting = true;

        // 更新UI - 使用更安全的方法
        const startBtn = document.getElementById('af-start-recording');
        const stopBtn = document.getElementById('af-stop-recording');
        const status = document.getElementById('af-status');
        const statusText = document.getElementById('af-status-text');

        if (startBtn) startBtn.style.display = 'none';
        if (stopBtn) stopBtn.style.display = 'block';
        if (status) status.classList.add('recording');
        if (statusText) statusText.textContent = t('clickToRecord');
        showNotification(t('recordingMode'));

        // 高亮所有表单元素
        highlightFormElements();

        // 添加点击事件监听器
        document.addEventListener('click', handleElementClick, true);

        showNotification('录制模式已开启,点击要自动填写的表单元素');
    }

    // 停止录制
    function stopRecording() {
        isRecording = false;
        isSelecting = false;

        // 更新UI - 使用更安全的方法
        const startBtn = document.getElementById('af-start-recording');
        const stopBtn = document.getElementById('af-stop-recording');
        const status = document.getElementById('af-status');
        const statusText = document.getElementById('af-status-text');

        if (startBtn) startBtn.style.display = 'block';
        if (stopBtn) stopBtn.style.display = 'none';
        if (status) {
            status.classList.remove('recording');
            status.classList.remove('selecting');
        }
        if (statusText) statusText.textContent = t('recordingStopped');
        showNotification(t('recordingStopped'));

        // 移除高亮
        removeHighlights();

        // 移除事件监听器
        document.removeEventListener('click', handleElementClick, true);

        // 保存规则
        saveRules();

        // 更新规则列表
        updateRulesList();

        showNotification('录制已停止,规则已保存');
    }
    // 高亮表单元素
    function highlightFormElements() {
        const formElements = document.querySelectorAll('input, textarea, select');
        formElements.forEach(el => {
            // 检查这个元素是否已经有规则
            const selector = generateSelector(el);
            if (currentRules[selector]) {
                el.classList.add('af-recorded');
            } else {
                el.classList.add('af-highlight');
            }
        });
    }

    // 移除高亮
    function removeHighlights() {
        const formElements = document.querySelectorAll('input, textarea, select');
        formElements.forEach(el => {
            el.classList.remove('af-highlight');
            // 保留已录制元素的高亮
            if (!currentRules[generateSelector(el)]) {
                el.classList.remove('af-recorded');
            }
        });
    }

    // 处理元素点击
    function handleElementClick(e) {
        if (!isSelecting) return;

        const element = e.target;
        if (element.matches('input, textarea, select')) {
            e.preventDefault();
            e.stopPropagation();

            isSelecting = false;
            const status = document.getElementById('af-status');
            const statusText = document.getElementById('af-status-text');

            if (status) status.classList.add('selecting');
            if (statusText) statusText.textContent = t('enterValue');

            const selector = generateSelector(element);
            console.log('最终生成的选择器:', selector);
             let currentValue = currentRules[selector] || element.value || '';

            // 弹出对话框让用户输入值
            // const value = prompt(`请输入要为该元素填写的值:\n\n元素: ${selector}`, currentValue);

             // 为复选框和单选按钮提供更好的提示
            let promptMessage = `请输入要为该元素填写的值:\n\n元素: ${selector}`;

            if (element.type === 'checkbox' || element.type === 'radio') {
                promptMessage += `\n\n提示:对于复选框/单选按钮,可以输入:\n- "true" 或 "checked" 表示选中\n- "false" 表示取消选中\n- 或者输入选项的实际值`;

                // 如果当前是选中的,设置默认值为 true
                if (element.checked) {
                    currentValue = 'true';
                }
            }

        const value = prompt(promptMessage, currentValue);

            if (value !== null) {
                currentRules[selector] = value;
                element.classList.remove('af-highlight');
                element.classList.add('af-recorded');
                showNotification(`已为元素设置值: ${value}`);
            }

            isSelecting = true;
            if (status) status.classList.remove('selecting');
            if (statusText) statusText.textContent = t('clickToRecord');
        }
    }
        // 生成元素选择器
        function generateSelector(element) {
            console.log('=== 生成选择器调试 ===');
            console.log('元素信息:', {
                tagName: element.tagName,
                id: element.id,
                name: element.name,
                className: element.className,
                placeholder: element.placeholder,
                type: element.type
            });

            // 1. 首先尝试生成当前元素的选择器
            let selector = generateElementSelector(element);
            console.log('初始选择器:', selector);

             // 2. 如果开启了唯一性模式,检查选择器的唯一性
            if (uniqueMode) {
                let elements = findElementsBySelector(selector);
                console.log('初始选择器匹配元素数量:', elements.length);

                // 3. 如果选择器不唯一,尝试向上查找父元素来生成更精确的选择器
                if (elements.length > 1) {
                    console.log('选择器不唯一,尝试向上查找更精确的选择器');
                    selector = findUniqueSelector(element);
                    console.log('最终选择器:', selector);
                }
            }
            return selector;
        }

        // 生成当前元素的选择器(不过滤临时类)
        function generateElementSelector(element) {
            // 优先使用ID
            if (element.id) {
                return `#${escapeCSS(element.id)}`;
            }

            // 使用name属性
            if (element.name) {
                return `[name="${escapeCSS(element.name)}"]`;
            }

            // 检查各种可能的唯一属性(新增)
            const uniqueAttributes = ['prop', 'data-id', 'name', 'field', 'key', 'data-prop', 'v-model', 'ng-model'];
            for (const attr of uniqueAttributes) {
                if (element.hasAttribute(attr)) {
                    const attrValue = element.getAttribute(attr);
                    if (attrValue) {
                        const attrSelector = `[${attr}="${escapeCSS(attrValue)}"]`;
                        if (isSelectorUnique(attrSelector)) {
                            return attrSelector;
                        }
                    }
                }
            }

            // 使用类名和标签名
            if (element.className && typeof element.className === 'string') {
                const classes = element.className.split(' ')
                    .filter(c => c && !c.includes('af-highlight') && !c.includes('af-recorded'))
                    .join('.');
                if (classes) {
                    return `${element.tagName.toLowerCase()}.${escapeCSS(classes)}`;
                }
            }

            // 使用标签名和属性
            let selector = element.tagName.toLowerCase();
            if (element.placeholder) {
                return `${selector}[placeholder="${escapeCSS(element.placeholder)}"]`;
            }

            if (element.type) {
                return `${selector}[type="${escapeCSS(element.type)}"]`;
            }

            // 最后使用标签名
            return selector;
        }

        // 检查选择器是否唯一
        function isSelectorUnique(selector) {
            try {
                const elements = document.querySelectorAll(selector);
                return elements.length === 1;
            } catch (error) {
                console.error('选择器唯一性检查错误:', selector, error);
                return false;
            }
        }

        // 查找唯一选择器
        function findUniqueSelector(element, maxDepth = 5) {
            let currentElement = element;
            let depth = 0;

            // 首先检查元素本身是否有唯一属性
            const directSelector = generateElementSelector(element);
            if (isSelectorUnique(directSelector)) {
                return directSelector;
            }

            // 检查prop属性(单独处理)
            if (element.hasAttribute('prop')) {
                const propValue = element.getAttribute('prop');
                const propSelector = `[prop="${escapeCSS(propValue)}"]`;
                if (isSelectorUnique(propSelector)) {
                    return propSelector;
                }
            }

            // 向上查找父元素
            while (currentElement && depth < maxDepth) {
                // 尝试生成当前元素的选择器
                let selector = generateElementSelector(currentElement);

                // 如果当前元素有ID,直接返回(ID应该是唯一的)
                if (currentElement.id) {
                    return `#${escapeCSS(currentElement.id)} ${element.tagName.toLowerCase()}`;
                }

                // 检查选择器的唯一性
                let elements = findElementsBySelector(selector);
                if (elements.length === 1) {
                    // 如果当前元素选择器唯一,但我们需要定位到原始元素
                    // 添加后代选择器来定位原始元素
                    return getPathSelector(currentElement, element);
                }

                // 向上查找父元素
                currentElement = currentElement.parentElement;
                depth++;
            }

            // 如果所有尝试都失败,返回原始元素的选择器
            return generateElementSelector(element);
        }

        // 获取从父元素到子元素的路径选择器
        function getPathSelector(parentElement, targetElement) {
            let path = [];
            let current = targetElement;

            // 构建从目标元素到父元素的路径
            while (current && current !== parentElement) {
                path.unshift(generateElementSelector(current));
                current = current.parentElement;
            }

            // 添加父元素选择器
            if (parentElement) {
                path.unshift(generateElementSelector(parentElement));
            }

            return path.join(' ');
        }

        // 通过选择器查找元素
        function findElementsBySelector(selector) {
            try {
                if (selector.startsWith('//')) {
                    // XPath选择器
                    const result = document.evaluate(
                        selector,
                        document,
                        null,
                        XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
                        null
                    );

                    const elements = [];
                    for (let i = 0; i < result.snapshotLength; i++) {
                        elements.push(result.snapshotItem(i));
                    }
                    return elements;
                } else {
                    // CSS选择器
                    return document.querySelectorAll(selector);
                }
            } catch (error) {
                console.error('选择器查找错误:', selector, error);
                return [];
            }
        }

    // CSS转义函数
    function escapeCSS(str) {
        return str.replace(/([\\'"])/g, '\\$1');
    }
    // 生成XPath
    function getXPath(element) {
        if (element.id) return `//*[@id="${element.id}"]`;
        if (element === document.body) return '/html/body';

        let ix = 0;
        const siblings = element.parentNode.childNodes;

        for (let i = 0; i < siblings.length; i++) {
            const sibling = siblings[i];
            if (sibling === element) {
                return `${getXPath(element.parentNode)}/${element.tagName}[${ix + 1}]`;
            }
            if (sibling.nodeType === 1 && sibling.tagName === element.tagName) {
                ix++;
            }
        }
    }

    // 保存规则
    function saveRules() {
        const pageUrl = getPageIdentifier();
        const allRules = JSON.parse(GM_getValue('autoFillRules', '{}'));

        allRules[pageUrl] = currentRules;
        GM_setValue('autoFillRules', JSON.stringify(allRules));
    }

    // 获取页面标识符
    function getPageIdentifier() {
     //   return window.location.hostname + window.location.pathname;
          const url = window.location.href;
        if (pageMatchMode === 'loose') {
            // 宽松模式:提取页面特征,忽略变化的部分
            return extractPageSignature(url);
        } else {
            // 严格模式:使用完整URL
            return url;
        }
    }

    // 提取页面特征签名
    function extractPageSignature(url) {
        try {
            const urlObj = new URL(url);
            let signature = urlObj.hostname + urlObj.pathname;

            // 移除路径中的UUID和长数字ID
            signature = signature.replace(/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\//gi, '/*/');
            signature = signature.replace(/\/\d{8,}\//g, '/*/');

            // 移除查询参数中的特定键值对(可选)
            // 可以根据需要添加更多规则

            console.log('页面特征签名:', signature);
            return signature;
        } catch (error) {
            console.error('提取页面特征失败,使用完整URL:', error);
            return url;
        }
    }

    // 加载当前页面的规则
    function loadRulesForPage() {
        const pageUrl = getPageIdentifier();
        const allRules = JSON.parse(GM_getValue('autoFillRules', '{}'));
        currentRules = allRules[pageUrl] || {};

        // 高亮已录制的元素
        highlightRecordedElements();
    }
    // 高亮已录制的元素
    function highlightRecordedElements() {
        Object.keys(currentRules).forEach(selector => {
            try {
                let elements;

                if (selector.startsWith('//')) {
                    // XPath选择器
                    const result = document.evaluate(
                        selector,
                        document,
                        null,
                        XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
                        null
                    );

                    for (let i = 0; i < result.snapshotLength; i++) {
                        const element = result.snapshotItem(i);
                        if (element) element.classList.add('af-recorded');
                    }
                } else {
                    // CSS选择器
                    elements = document.querySelectorAll(selector);
                    elements.forEach(el => el.classList.add('af-recorded'));
                }
            } catch (error) {
                console.warn(`无法高亮元素: ${selector}`, error);
            }
        });
    }

    // 自动填写
    function autoFill() {
        let filledCount = 0;
        const pageUrl = getPageIdentifier();
        const allRules = JSON.parse(GM_getValue('autoFillRules', '{}'));
        const rules = allRules[pageUrl] || {};
        // 调试:打印当前页面的规则
        console.log('=== 自动填写调试信息 ===');
        console.log('当前页面URL:', pageUrl);
        console.log('所有规则:', allRules);
        console.log('当前页面规则:', rules);
        console.log('规则数量:', Object.keys(rules).length);

        if (Object.keys(rules).length === 0) {
            showNotification(t('noRulesWarning'), 'warning');
            return;
        }

        Object.keys(rules).forEach(selector => {
            console.log('处理选择器:', selector);

            try {
                let elements = findElementsBySelector(selector);
                console.log('找到元素数量:', elements.length);

                if (elements.length === 0) {
                    console.warn('没有找到匹配的元素,选择器可能已失效:', selector);
                    return; // 跳过这个选择器
                }

                if (elements.length > 1) {
                    console.warn('找到多个匹配元素,可能填写错误:', selector);
                }

                // 为所有匹配的元素填写值
                elements.forEach(element => {
                    const value = rules[selector];

                    if (element.tagName === 'SELECT') {
                        // 处理下拉框
                        let optionFound = false;
                        Array.from(element.options).some(option => {
                            if (option.text === value || option.value === value) {
                                option.selected = true;
                                optionFound = true;
                                element.dispatchEvent(new Event('change', { bubbles: true }));
                                return true;
                            }
                            return false;
                        });

                        if (optionFound) filledCount++;
                    } else if (element.type === 'checkbox') {
                    // 改进的复选框处理逻辑
                    const checkboxValue = element.value.toLowerCase();
                    const targetValue = value.toLowerCase();

                    // 多种匹配方式
                    const shouldCheck =
                        targetValue === 'true' ||
                        targetValue === 'checked' ||
                        targetValue === '1' ||
                        targetValue === 'on' ||
                        targetValue === checkboxValue ||
                        (element.nextElementSibling &&
                         element.nextElementSibling.textContent &&
                         element.nextElementSibling.textContent.toLowerCase().includes(targetValue));

                    element.checked = shouldCheck;
                    if (shouldCheck) {
                        element.dispatchEvent(new Event('change', { bubbles: true }));
                        filledCount++;
                    }
                } else if (element.type === 'radio') {
                    // 改进的单选按钮处理逻辑
                    const radioValue = element.value.toLowerCase();
                    const targetValue = value.toLowerCase();

                    // 多种匹配方式
                    const shouldSelect =
                        targetValue === 'true' ||
                        targetValue === 'checked' ||
                        targetValue === '1' ||
                        targetValue === 'on' ||
                        targetValue === radioValue ||
                        (element.nextElementSibling &&
                         element.nextElementSibling.textContent &&
                         element.nextElementSibling.textContent.toLowerCase().includes(targetValue));

                    if (shouldSelect) {
                        element.checked = true;
                        element.dispatchEvent(new Event('change', { bubbles: true }));
                        filledCount++;
                    }
                } else {
                        // 处理输入框和文本域
                        element.value = value;
                        element.dispatchEvent(new Event('input', { bubbles: true }));
                        element.dispatchEvent(new Event('change', { bubbles: true }));
                        filledCount++;
                    }
                });
            } catch (error) {
                console.error(`自动填写失败 - 选择器: ${selector}`, error);
            }
        });

        if (filledCount > 0) {
            showNotification(t('fillSuccess', { count: filledCount }), 'success');
        } else {
            showNotification(t('fillWarning'), 'warning');
        }
    }
    // 切换规则列表显示
    function toggleRulesList() {
        const rulesList = document.getElementById('af-rules-list');
        const manageBtn = document.getElementById('af-manage-rules');

        if (rulesList && manageBtn) {
            if (rulesList.style.display === 'none') {
                rulesList.style.display = 'block';
                manageBtn.textContent = '隐藏规则';
            } else {
                rulesList.style.display = 'none';
                manageBtn.textContent = '管理规则';
            }
        }
    }

    // 更新规则列表
    function updateRulesList() {
        const container = document.getElementById('af-rules-container');
        if (!container) return;

        container.innerHTML = '';

        if (Object.keys(currentRules).length === 0) {
            const li = document.createElement('li');
            li.textContent = t('noRules');
            li.style.color = '#999';
            li.style.fontStyle = 'italic';
            container.appendChild(li);
            return;
        }

        Object.keys(currentRules).forEach(selector => {
            const li = document.createElement('li');
            li.innerHTML = `
                <span class="af-rule-selector" title="${selector}">${truncateText(selector, 30)}</span>
                <span class="af-rule-value" title="${currentRules[selector]}">${truncateText(currentRules[selector], 20)}</span>
            `;
            container.appendChild(li);
        });
    }

    // 清除当前页面的规则
    function clearRules() {
        if (!confirm(t('clearConfirm'))) {
            return;
        }

        const pageUrl = getPageIdentifier();
        const allRules = JSON.parse(GM_getValue('autoFillRules', '{}'));

        delete allRules[pageUrl];
        GM_setValue('autoFillRules', JSON.stringify(allRules));

        currentRules = {};
        removeHighlights();
        updateRulesList();

        showNotification(t('clearedSuccess'), 'success');
    }
    // 截断文本
    function truncateText(text, maxLength) {
        if (!text || text.length <= maxLength) return text;
        return text.substring(0, maxLength) + '...';
    }

    // 显示通知
    function showNotification(message, type = 'info') {
        // 移除现有通知
        const existingNotification = document.getElementById('af-notification');
        if (existingNotification) {
            existingNotification.remove();
        }

        const notification = document.createElement('div');
        notification.id = 'af-notification';
        notification.textContent = message;

        // 根据类型设置样式
        let bgColor = '#3498db';
        if (type === 'success') bgColor = '#2ecc71';
        if (type === 'warning') bgColor = '#f39c12';
        if (type === 'error') bgColor = '#e74c3c';

        notification.style.cssText = `
            position: fixed;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: ${bgColor};
            color: white;
            padding: 12px 20px;
            border-radius: 4px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            z-index: 10001;
            font-size: 14px;
            transition: opacity 0.3s;
            max-width: 80%;
            text-align: center;
        `;

        document.body.appendChild(notification);

        // 3秒后自动消失
        setTimeout(() => {
            notification.style.opacity = '0';
            setTimeout(() => {
                if (notification.parentNode) {
                    notification.remove();
                }
            }, 300);
        }, 3000);
    }
    // 隐藏面板
    function hidePanel() {
        const panel = document.getElementById('auto-fill-panel');
        if (panel) {
            panel.remove();
        }

        // 移除所有高亮
        removeHighlights();

        // 如果正在录制,停止录制
        if (isRecording) {
            stopRecording();
        }
    }

    // 注册菜单命令
    function registerMenuCommands() {
        // 清理之前的菜单命令
        menuCommands.forEach(cmd => {
            try {
                GM_unregisterMenuCommand(cmd);
            } catch (e) {
                // 忽略取消注册错误
            }
        });
        menuCommands = [];

        // 注册新命令
        try {
            menuCommands.push(GM_registerMenuCommand('显示自动填写面板', createControlPanel));
            menuCommands.push(GM_registerMenuCommand('开始录制规则', startRecording));
            menuCommands.push(GM_registerMenuCommand('自动填写表单', autoFill));
        } catch (e) {
            console.warn('注册菜单命令失败:', e);
        }
    }

    // 初始化
    function init() {
        // 注册菜单命令
        registerMenuCommands();

        // 总是创建悬浮按钮
        createFloatingButton();

        loadUniqueMode();
        loadPageMatchMode();

        // 对于表单密集的页面,自动显示面板
        const formCount = document.querySelectorAll('form, input, textarea, select').length;
        if (formCount > 2) {
            setTimeout(createControlPanel, 1000);
        }
    }

    // 页面加载完成后初始化
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();