// ==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 // @downloadURL none // ==/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 = `
📝 智能表单填充
`; 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 = `
姓名: ${user.name.first} ${user.name.last}
性别: ${user.gender}
电话: ${user.formattedPhone}
地址: ${user.location.street.number} ${user.location.street.name}
城市: ${user.location.city}
州/省: ${user.location.state}
邮编: ${user.location.postcode}
国家: ${user.location.country} (${selectedCountryCode})
`; 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 = `
${message}
`; 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表单填充助手 已加载!'); })();