// ==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表单填充助手 已加载!');
})();