Greasy Fork

Greasy Fork is available in English.

Whcms表单填充助手

使用RandomUser API自动生成并填充网页注册表单。

当前为 2025-07-11 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Whcms表单填充助手
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  使用RandomUser API自动生成并填充网页注册表单。
// @author       Assistant (Enhanced by AI)
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @connect      randomuser.me
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 国家代码 -> 中文名
    const countryNamesCN = {
        'AU': '澳大利亚', 'BR': '巴西', 'CA': '加拿大', 'CH': '瑞士', 'DE': '德国',
        'DK': '丹麦', 'ES': '西班牙', 'FI': '芬兰', 'FR': '法国', 'GB': '英国',
        'IE': '爱尔兰', 'IN': '印度', 'IR': '伊朗', 'MX': '墨西哥', 'NL': '荷兰',
        'NO': '挪威', 'NZ': '新西兰', 'RS': '塞尔维亚', 'TR': '土耳其', 'UA': '乌克兰', 'US': '美国'
    };

    // 国家代码 -> 电话区号
    const countryPhoneCodes = {
        'AU': '+61', 'BR': '+55', 'CA': '+1', 'CH': '+41', 'DE': '+49', 'DK': '+45',
        'ES': '+34', 'FI': '+358', 'FR': '+33', 'GB': '+44', 'IE': '+353', 'IN': '+91',
        'IR': '+98', 'MX': '+52', 'NL': '+31', 'NO': '+47', 'NZ': '+64', 'RS': '+381',
        'TR': '+90', 'UA': '+380', 'US': '+1'
    };

    let currentUserData = null;

    GM_addStyle(`
        #form-filler-panel {
            position: fixed; top: 20px; right: 20px; width: 280px; background: #fff;
            border: 1px solid #ccc; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            z-index: 10000; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            font-size: 14px;
        }
        #form-filler-header {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 12px 15px;
            border-radius: 8px 8px 0 0; cursor: move; display: flex; justify-content: space-between; align-items: center;
        }
        #form-filler-content { padding: 15px; }
        .form-group { margin-bottom: 12px; }
        .form-group label { display: block; margin-bottom: 4px; font-weight: bold; color: #333; }
        .form-group select, .form-group button {
            width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; box-sizing: border-box;
        }
        .form-group button {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; cursor: pointer;
            font-weight: bold; transition: all 0.3s;
        }
        .form-group button:hover { transform: translateY(-1px); box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); }
        .form-group button:disabled { background: #ccc; cursor: not-allowed; transform: none; box-shadow: none; }
        #user-preview {
            background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; padding: 10px; margin-top: 12px;
            font-size: 12px; max-height: 200px; overflow-y: auto;
        }
        .preview-item { margin-bottom: 4px; display: flex; justify-content: space-between; align-items: flex-start; }
        .preview-label { font-weight: bold; color: #495057; margin-right: 8px; white-space: nowrap; }
        .preview-value { color: #6c757d; text-align: right; max-width: 170px; word-break: break-all; }
        .close-btn { background: none; border: none; color: white; font-size: 18px; cursor: pointer; padding: 0; width: 24px; height: 24px; text-align: center; line-height: 24px; }
        #form-filler-toggle {
            position: fixed; top: 20px; right: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white; border: none; padding: 10px 15px; border-radius: 20px; cursor: pointer; z-index: 9999;
            font-weight: bold; box-shadow: 0 2px 8px rgba(0,0,0,0.2); display: none;
        }
        .status-message { padding: 8px; border-radius: 4px; margin-top: 8px; text-align: center; font-size: 12px; }
        .status-success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
        .status-error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
        .status-loading { background: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; }
    `);

    // 字段匹配规则
    const fieldMappings = {
        firstName: ['firstname', 'first_name', 'first-name', 'fname', 'given_name', 'givenname'],
        lastName: ['lastname', 'last_name', 'last-name', 'lname', 'surname', 'family_name', 'familyname'],
        fullName: ['fullname', 'full_name', 'full-name', 'name', 'username', 'user_name', 'user-name'],
        phone: ['phone', 'phonenumber', 'phone_number', 'phone-number', 'tel', 'telephone', 'mobile', 'cell'],
        address: ['address', 'street', 'streetaddress', 'street_address', 'street-address', 'address1', 'addr1'],
        city: ['city', 'town', 'locality'],
        state: ['state', 'province', 'region', 'stateprovince', 'state_province', 'state-province'],
        postcode: ['postcode', 'zipcode', 'zip', 'postal', 'postalcode', 'postal_code', 'postal-code', 'zip_code'],
        country: ['country', 'nation', 'nationality'],
        gender: ['gender', 'sex']
    };


    function createPanel() {
        const toggleButton = document.createElement('button');
        toggleButton.id = 'form-filler-toggle';
        toggleButton.textContent = '📝 表单填充';
        document.body.appendChild(toggleButton);

        const panel = document.createElement('div');
        panel.id = 'form-filler-panel';
        panel.style.display = 'none';
        panel.innerHTML = `
            <div id="form-filler-header">
                <span>📝 智能表单填充</span>
                <button class="close-btn" id="close-panel">×</button>
            </div>
            <div id="form-filler-content">
                <div class="form-group">
                    <label for="country-select">选择国家/地区:</label>
                    <select id="country-select">
                        ${Object.entries(countryNamesCN).map(([code, name]) => 
                            `<option value="${code}">${name} (${code})</option>`
                        ).join('')}
                    </select>
                </div>
                <div class="form-group"><button id="generate-btn">🎲 生成用户信息</button></div>
                <div class="form-group"><button id="fill-btn" disabled>🖊️ 自动填充表单</button></div>
                <div class="form-group"><button id="clear-btn">🗑️ 清空表单</button></div>
                <div id="user-preview" style="display: none;"></div>
                <div id="status-message"></div>
            </div>
        `;
        document.body.appendChild(panel);

        const savedCountry = GM_getValue('selectedCountry', 'US');
        document.getElementById('country-select').value = savedCountry;

        bindEvents();
        makeDraggable(panel);
    }

    function bindEvents() {
        document.getElementById('form-filler-toggle').addEventListener('click', togglePanel);
        document.getElementById('close-panel').addEventListener('click', hidePanel);
        document.getElementById('generate-btn').addEventListener('click', generateUserData);
        document.getElementById('fill-btn').addEventListener('click', fillForm);
        document.getElementById('clear-btn').addEventListener('click', clearForm);
        document.getElementById('country-select').addEventListener('change', function() {
            GM_setValue('selectedCountry', this.value);
        });
    }

    function generateUserData() {
        const countryCode = document.getElementById('country-select').value;
        const generateBtn = document.getElementById('generate-btn');
        const fillBtn = document.getElementById('fill-btn');
        
        generateBtn.disabled = true;
        generateBtn.textContent = '⏳ 生成中...';
        showStatus('正在生成用户信息...', 'loading');

        GM_xmlhttpRequest({
            method: 'GET',
            url: `https://randomuser.me/api/?nat=${countryCode}&inc=name,location,phone,cell,dob,gender`,
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    if (data.results && data.results.length > 0) {
                        currentUserData = data.results[0];
                        const phoneCode = countryPhoneCodes[countryCode] || '';
                        const rawPhone = (currentUserData.phone || currentUserData.cell).replace(/[^\d]/g, '');
                        currentUserData.formattedPhone = phoneCode + rawPhone;
                        displayUserPreview();
                        fillBtn.disabled = false;
                        showStatus('✅ 用户信息生成成功!', 'success');
                    } else { throw new Error('API未返回有效用户数据'); }
                } catch (error) {
                    console.error('解析用户数据失败:', error);
                    showStatus('❌ 生成失败,请重试', 'error');
                } finally {
                    generateBtn.disabled = false;
                    generateBtn.textContent = '🎲 生成用户信息';
                }
            },
            onerror: function(error) {
                console.error('API请求失败:', error);
                showStatus('❌ 网络请求失败,请检查网络', 'error');
                generateBtn.disabled = false;
                generateBtn.textContent = '🎲 生成用户信息';
            }
        });
    }

    function displayUserPreview() {
        if (!currentUserData) return;
        const preview = document.getElementById('user-preview');
        const user = currentUserData;
        const selectedCountryCode = document.getElementById('country-select').value;
        
        preview.innerHTML = `
            <div class="preview-item"><span class="preview-label">姓名:</span> <span class="preview-value">${user.name.first} ${user.name.last}</span></div>
            <div class="preview-item"><span class="preview-label">性别:</span> <span class="preview-value">${user.gender}</span></div>
            <div class="preview-item"><span class="preview-label">电话:</span> <span class="preview-value">${user.formattedPhone}</span></div>
            <div class="preview-item"><span class="preview-label">地址:</span> <span class="preview-value">${user.location.street.number} ${user.location.street.name}</span></div>
            <div class="preview-item"><span class="preview-label">城市:</span> <span class="preview-value">${user.location.city}</span></div>
            <div class="preview-item"><span class="preview-label">州/省:</span> <span class="preview-value">${user.location.state}</span></div>
            <div class="preview-item"><span class="preview-label">邮编:</span> <span class="preview-value">${user.location.postcode}</span></div>
            <div class="preview-item"><span class="preview-label">国家:</span> <span class="preview-value">${user.location.country} (${selectedCountryCode})</span></div>
        `;
        preview.style.display = 'block';
    }
    
    function findInputFields() {
        const inputs = document.querySelectorAll('input[type="text"], input[type="tel"], input[type="email"], input:not([type]), select, textarea');
        const fields = {};
        for (const [fieldType, patterns] of Object.entries(fieldMappings)) {
            if (fields[fieldType]) continue;
            for (const input of inputs) {
                const attributes = [input.name, input.id, input.placeholder, input.className, input.getAttribute('data-field'), input.getAttribute('autocomplete')].filter(Boolean).join(' ').toLowerCase();
                let labelText = '';
                const label = input.closest('label') || (input.id && document.querySelector(`label[for="${input.id}"]`));
                if (label) labelText = label.textContent.toLowerCase();
                const allText = `${attributes} ${labelText}`;
                if (fieldType !== 'fullName' && fieldType !== 'firstName' && fieldType !== 'lastName' && (input.type === 'email' || input.type === 'password' || allText.includes('email') || allText.includes('password'))) {
                    continue;
                }
                for (const pattern of patterns) {
                    if (allText.includes(pattern)) {
                        fields[fieldType] = input;
                        break; 
                    }
                }
                if (fields[fieldType]) break;
            }
        }
        return fields;
    }

    function setFieldValue(element, value) {
        if (!element || typeof value === 'undefined') return false;
        element.focus();
        if (element.tagName.toLowerCase() === 'select') {
            const valueLower = String(value).toLowerCase();
            let optionFound = false;
            for (const option of element.options) {
                if (String(option.value).toLowerCase() === valueLower || String(option.textContent).toLowerCase() === valueLower) {
                    element.value = option.value;
                    optionFound = true;
                    break;
                }
            }
            if (!optionFound) {
                 for (const option of element.options) {
                    if (String(option.textContent).toLowerCase().includes(valueLower)) {
                        element.value = option.value;
                        optionFound = true;
                        break;
                    }
                }
            }
            if(!optionFound) return false;
        } else {
            element.value = value;
        }
        ['input', 'change', 'blur', 'keyup'].forEach(eventType => {
            element.dispatchEvent(new Event(eventType, { bubbles: true, cancelable: true }));
        });
        element.style.backgroundColor = '#e8f5e8';
        setTimeout(() => { element.style.backgroundColor = ''; }, 1500);
        return true;
    }

    function fillForm() {
        if (!currentUserData) {
            showStatus('❌ 请先生成用户信息', 'error');
            return;
        }

        const fieldsToFill = findInputFields();
        const user = currentUserData;
        let filledCount = 0;

        if (fieldsToFill.firstName && fieldsToFill.lastName) {
            delete fieldsToFill.fullName;
        }

        const fillData = {
            firstName: user.name.first,
            lastName: user.name.last,
            fullName: `${user.name.first} ${user.name.last}`,
            phone: user.formattedPhone,
            address: `${user.location.street.number} ${user.location.street.name}`,
            city: user.location.city,
            state: user.location.state,
            postcode: user.location.postcode.toString(),
            country: user.location.country,
            gender: user.gender
        };

        const countryField = fieldsToFill.country;
        const stateField = fieldsToFill.state;

        if (countryField && stateField && stateField.tagName.toLowerCase() === 'select') {
            if (setFieldValue(countryField, fillData.country)) {
                filledCount++;
            }
            const stateSelectElement = stateField;
            const stateNameToFill = fillData.state;

            if (setFieldValue(stateSelectElement, stateNameToFill)) {
                filledCount++;
            } else {
                console.log(`State "${stateNameToFill}" not found. Attaching MutationObserver.`);
                const observer = new MutationObserver((mutationsList, obs) => {
                    for (const mutation of mutationsList) {
                        if (mutation.type === 'childList') {
                            console.log('State dropdown options changed. Re-attempting to fill.');
                            if (setFieldValue(stateSelectElement, stateNameToFill)) {
                                filledCount++;
                                obs.disconnect();
                                clearTimeout(timeoutId);
                                return;
                            }
                        }
                    }
                });

                observer.observe(stateSelectElement, { childList: true });

                const timeoutId = setTimeout(() => {
                    observer.disconnect();
                    console.warn(`Observer for state timed out. Could not fill "${stateNameToFill}".`);
                }, 3000);
            }

            delete fieldsToFill.country;
            delete fieldsToFill.state;
        }

        for (const fieldType in fieldsToFill) {
            const element = fieldsToFill[fieldType];
            const dataToFill = fillData[fieldType];
            if (element && typeof dataToFill !== 'undefined') {
                if (setFieldValue(element, dataToFill)) {
                    filledCount++;
                }
            }
        }

        setTimeout(() => {
             if (filledCount > 0) {
                showStatus(`✅ 成功填充 ${filledCount} 个字段`, 'success');
            } else {
                showStatus('⚠️ 未找到可填充的字段', 'error');
            }
        }, 500);
    }

    function clearForm() {
        const inputs = document.querySelectorAll('input[type="text"], input[type="tel"], input:not([type]), select, textarea');
        let clearedCount = 0;
        inputs.forEach(input => {
            if (input.type === 'email' || input.type === 'password') return;
            const attributes = [input.name, input.id, input.placeholder, input.className].filter(Boolean).join(' ').toLowerCase();
            if (attributes.includes('email') || attributes.includes('password')) return;
            if (input.value.trim() !== '' || (input.tagName === 'SELECT' && input.selectedIndex > 0)) {
                if (input.tagName === 'SELECT') {
                    input.selectedIndex = 0;
                } else {
                    input.value = '';
                }
                ['input', 'change', 'blur'].forEach(e => input.dispatchEvent(new Event(e, { bubbles: true })));
                input.style.backgroundColor = '#ffe8e8';
                setTimeout(() => { input.style.backgroundColor = ''; }, 500);
                clearedCount++;
            }
        });
        if (clearedCount > 0) {
            showStatus(`🗑️ 已清空 ${clearedCount} 个字段`, 'success');
        } else {
            showStatus('ℹ️ 没有需要清空的字段', 'success');
        }
    }

    // --- UI/辅助函数 ---
    function togglePanel() {
        const panel = document.getElementById('form-filler-panel');
        const toggleBtn = document.getElementById('form-filler-toggle');
        if (panel.style.display === 'none') {
            panel.style.display = 'block';
            toggleBtn.style.display = 'none';
        } else {
            panel.style.display = 'none';
            updateButtonVisibility();
        }
    }

    function hidePanel() {
        document.getElementById('form-filler-panel').style.display = 'none';
        updateButtonVisibility();
    }

    function makeDraggable(element) {
        let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
        const header = document.getElementById('form-filler-header');
        if (header) header.onmousedown = dragMouseDown;
        function dragMouseDown(e) {
            e = e || window.event; e.preventDefault();
            pos3 = e.clientX; pos4 = e.clientY;
            document.onmouseup = closeDragElement; document.onmousemove = elementDrag;
        }
        function elementDrag(e) {
            e = e || window.event; e.preventDefault();
            pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY;
            pos3 = e.clientX; pos4 = e.clientY;
            element.style.top = (element.offsetTop - pos2) + "px";
            element.style.left = (element.offsetLeft - pos1) + "px";
            element.style.right = 'auto';
        }
        function closeDragElement() {
            document.onmouseup = null; document.onmousemove = null;
        }
    }

    function showStatus(message, type = 'loading') {
        const statusDiv = document.getElementById('status-message');
        if (!statusDiv) return;
        statusDiv.innerHTML = `<div class="status-message status-${type}">${message}</div>`;
        if (type !== 'loading') {
            setTimeout(() => { if (statusDiv) statusDiv.innerHTML = ''; }, 3000);
        }
    }

    function shouldShowButton() {
        if (GM_getValue('forceShowButton', false)) return true;
        return Object.keys(findInputFields()).length >= 2;
    }

    function updateButtonVisibility() {
        const toggleBtn = document.getElementById('form-filler-toggle');
        if (toggleBtn) toggleBtn.style.display = shouldShowButton() ? 'block' : 'none';
    }

    function registerMenuCommands() {
        if (window.menuCommandId) GM_unregisterMenuCommand(window.menuCommandId);
        const isForced = GM_getValue('forceShowButton', false);
        const commandLabel = isForced ? '悬浮按钮: 手动 (点击切换为自动)' : '悬浮按钮: 自动 (点击切换为手动)';
        window.menuCommandId = GM_registerMenuCommand(commandLabel, () => {
            const newSetting = !GM_getValue('forceShowButton', false);
            GM_setValue('forceShowButton', newSetting);
            alert(`智能填充助手:悬浮按钮已切换为 "${newSetting ? '手动显示' : '自动检测'}" 模式。`);
            registerMenuCommands();
            updateButtonVisibility();
        });
    }

    function init() {
        createPanel();
        registerMenuCommands();
        window.addEventListener('load', updateButtonVisibility);
    }

    init();
    console.log('Whcms表单填充助手 已加载!');

})();