// ==UserScript== // @name Whmcs表单填充助手 // @namespace http://tampermonkey.net/ // @version 1.1 // @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 https://update.greasyfork.icu/scripts/542304/Whmcs%E8%A1%A8%E5%8D%95%E5%A1%AB%E5%85%85%E5%8A%A9%E6%89%8B.user.js // @updateURL https://update.greasyfork.icu/scripts/542304/Whmcs%E8%A1%A8%E5%8D%95%E5%A1%AB%E5%85%85%E5%8A%A9%E6%89%8B.meta.js // ==/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; /* Use fixed positioning for the button */ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; padding: 10px 15px; border-radius: 20px; cursor: move; /* Change cursor to move */ 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 = '📝 表单填充'; const savedPos = GM_getValue('buttonPosition', null); if (savedPos) { toggleButton.style.top = savedPos.top; toggleButton.style.left = savedPos.left; toggleButton.style.right = 'auto'; toggleButton.style.bottom = 'auto'; } else { toggleButton.style.top = '20px'; toggleButton.style.right = '20px'; } 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, 'form-filler-header'); makeButtonDraggable(toggleButton); } function bindEvents() { document.getElementById('form-filler-toggle').addEventListener('click', (e) => { if (e.detail === 1) { 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] || ''; currentUserData.formattedPhone = phoneCode + (currentUserData.phone || currentUserData.cell).replace(/[^\d]/g, ''); 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'), user = currentUserData, code = 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} (${code})
`; 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 attrs = [input.name, input.id, input.placeholder, input.className, input.getAttribute('data-field'), input.getAttribute('autocomplete')].filter(Boolean).join(' ').toLowerCase(); let label = input.closest('label') || (input.id && document.querySelector(`label[for="${input.id}"]`)); const allText = `${attrs} ${label ? label.textContent.toLowerCase() : ''}`; 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 valLower = String(value).toLowerCase(); let found = false; for (const opt of element.options) { if (String(opt.value).toLowerCase() === valLower || String(opt.textContent).toLowerCase() === valLower) { element.value = opt.value; found = true; break; } } if (!found) { for (const opt of element.options) { if (String(opt.textContent).toLowerCase().includes(valLower)) { element.value = opt.value; found = true; break; } } } if (!found) return false; } else { element.value = value; } ['input', 'change', 'blur', 'keyup'].forEach(e => element.dispatchEvent(new Event(e, { 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(), 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, stateField = fieldsToFill.state; if (countryField && stateField && stateField.tagName.toLowerCase() === 'select') { if (setFieldValue(countryField, fillData.country)) filledCount++; const stateSelect = stateField, stateName = fillData.state; if (setFieldValue(stateSelect, stateName)) { filledCount++; } else { const observer = new MutationObserver((_, obs) => { if (setFieldValue(stateSelect, stateName)) { filledCount++; obs.disconnect(); clearTimeout(timeoutId); } }); observer.observe(stateSelect, { childList: true }); const timeoutId = setTimeout(() => { observer.disconnect(); console.warn(`Observer timed out for state: ${stateName}`); }, 3000); } delete fieldsToFill.country; delete fieldsToFill.state; } for (const fieldType in fieldsToFill) { if (fieldsToFill[fieldType] && setFieldValue(fieldsToFill[fieldType], fillData[fieldType])) filledCount++; } setTimeout(() => { showStatus(filledCount > 0 ? `✅ 成功填充 ${filledCount} 个字段` : '⚠️ 未找到可填充的字段', filledCount > 0 ? 'success' : '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 => { const attrs = [input.name, input.id, input.placeholder, input.className].filter(Boolean).join(' ').toLowerCase(); if (input.type === 'email' || input.type === 'password' || attrs.includes('email') || attrs.includes('password')) return; if (input.value.trim() !== '' || (input.tagName === 'SELECT' && input.selectedIndex > 0)) { input.value = (input.tagName === 'SELECT') ? input.options[0].value : ''; ['input', 'change', 'blur'].forEach(e => input.dispatchEvent(new Event(e, { bubbles: true }))); input.style.backgroundColor = '#ffe8e8'; setTimeout(() => { input.style.backgroundColor = ''; }, 500); clearedCount++; } }); showStatus(clearedCount > 0 ? `🗑️ 已清空 ${clearedCount} 个字段` : 'ℹ️ 没有需要清空的字段', 'success'); } function togglePanel() { const panel = document.getElementById('form-filler-panel'), 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, handleId) { let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; const handle = document.getElementById(handleId); if (handle) handle.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'; element.style.bottom = 'auto'; } function closeDragElement() { document.onmouseup = null; document.onmousemove = null; } } function makeButtonDraggable(button) { let offsetX, offsetY, isDragging = false; button.onmousedown = function(e) { isDragging = true; button.style.cursor = 'grabbing'; e.preventDefault(); offsetX = e.clientX - button.getBoundingClientRect().left; offsetY = e.clientY - button.getBoundingClientRect().top; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); }; function onMouseMove(e) { if (!isDragging) return; let newLeft = e.clientX - offsetX; let newTop = e.clientY - offsetY; button.style.left = `${newLeft}px`; button.style.top = `${newTop}px`; button.style.right = 'auto'; button.style.bottom = 'auto'; } function onMouseUp() { if (!isDragging) return; isDragging = false; button.style.cursor = 'move'; document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); GM_setValue('buttonPosition', { top: button.style.top, left: button.style.left }); } } 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 >= 4; } 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 label = isForced ? '悬浮按钮: 手动 (点击切换为自动)' : '悬浮按钮: 自动 (点击切换为手动)'; window.menuCommandId = GM_registerMenuCommand(label, () => { 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表单填充助手已加载!'); })();