// ==UserScript== // @name 统计云自动登陆 // @namespace http://tampermonkey.net/ // @version 3.3 // @description 自动处理网站登录流程,包括验证码识别、账号管理等 // @author hanj-cn@qq.com // @match http://10.42.181.55:8800/dg/page.html* // @match https://tjyhome.stats.gov.cn/platform/page.html* // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @require https://code.jquery.com/jquery-3.6.0.min.js // @license MIT // ==/UserScript== // 2026-03-11 v3.3 // - 优化了控制面板3个checkbox的联动逻辑,增强用户体验 // - 修改了配色 (function () { 'use strict'; // 读取脚本名 const scriptName = GM_info.script.name // 读取版本号 const version = GM_info.script.version; // 读取作者 const author = GM_info.script.author; // ==================== 配置部分 ==================== // 请根据目标网站修改以下配置 const CONFIG = { // DOM选择器配置(使用XPath) selectors: { usernameInput: '//*[@id="app"]/div/div/div[2]/div/div[3]/div[2]/div/form/div[1]/div/div/input', // 用户名输入框XPath passwordInput: '/html/body/div/input', // 密码输入框XPath captchaInput: '//*[@id="app"]/div/div/div[2]/div/div[3]/div[2]/div/form/div[3]/div/div/input', // 验证码输入框XPath captchaImage: '//*[@id="app"]/div/div/div[2]/div/div[3]/div[2]/div/form/div[3]/div/div/div/img', // 验证码图片XPath loginButton: '//*[@id="app"]/div/div/div[2]/div/div[3]/div[2]/div/form/button', // 登录按钮XPath }, // 延时配置(毫秒) delays: { pageLoad: 1000, // 页面加载等待时间 captchaLoad: 500, // 验证码图片加载等待时间 }, // 开关控制 enabled: true, // 脚本总开关 autoCaptcha: true, // 自动验证码开关 autoLogin: true, // 自动登录开关 }; // ==================== 账号管理 ==================== // 当前站点host,用于区分不同网站的存储 const host = window.location.host; console.log('当前站点host:', host); // 从 Tampermonkey 存储中读取账号数据(按 host 区分) let accounts = GM_getValue('accounts_' + host, []); let defaultAccountIndex = GM_getValue('defaultAccountIndex_' + host, -1); console.log('加载账号数据:', accounts, '默认账号索引:', defaultAccountIndex); // 验证码及图片上传 API 密钥 let captchaApiKey = GM_getValue('captchaApiKey', ''); let uploadApiKey = GM_getValue('uploadApiKey', ''); // imgbb API key console.log('加载API密钥:', { captchaApiKey: !!captchaApiKey, uploadApiKey: !!uploadApiKey }); // 获取默认账号 function getDefaultAccount() { if (defaultAccountIndex >= 0 && defaultAccountIndex < accounts.length) { console.log('使用默认账号:', accounts[defaultAccountIndex].username); return accounts[defaultAccountIndex]; } else if (accounts.length > 0) { // 如果没有设置默认账号,使用第一个账号 console.log('未设置默认账号,使用第一个账号:', accounts[0].username); return accounts[0]; } return null; } // 保存账号 function saveAccounts() { GM_setValue('accounts_' + host, accounts); GM_setValue('defaultAccountIndex_' + host, defaultAccountIndex); console.log('账号数据已保存', accounts, '默认账号索引:', defaultAccountIndex); } // ==================== 验证码识别接口 ==================== // 上传图片获取URL async function uploadImage(base64Data) { console.log('开始上传base64图片到外部服务:', base64Data); if (!uploadApiKey) { console.error('未设置图片上传API密钥'); return null; } return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: 'https://api.imgbb.com/1/upload', data: `key=${uploadApiKey}&image=${encodeURIComponent(base64Data)}`, headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, onload: (response) => { try { const result = JSON.parse(response.responseText); if (result.success) { resolve(result.data.url); } else { reject(new Error('上传失败: ' + result.error.message)); } } catch (e) { reject(e); } }, onerror: reject }); }); } // 自定义验证码识别函数 // 参数:imageUrl - 验证码图片的URL // 返回:Promise - 识别结果 async function recognizeCaptcha(imageUrl) { console.log('开始识别验证码'); // 默认实现:对接xxapi.cn // 请在配置界面输入您的API密钥 if (!captchaApiKey) { showToast('ocr密钥未设置,无法识别验证码'); return ''; } console.log('开始识别验证码,图片URL:', imageUrl); try { let finalUrl = imageUrl; if (imageUrl.startsWith('data:image/')) { // 如果是data URL,先上传获取外部URL console.log('检测到data URL,开始上传图片'); const base64Data = imageUrl.split(',')[1]; finalUrl = await uploadImage(base64Data); if (!finalUrl) { console.error('图片上传失败'); return ''; } console.log('上传成功,外部URL:', finalUrl); } const API_URL = `https://v2.xxapi.cn/api/ocr?url=${encodeURIComponent(finalUrl)}`; console.log('发送验证码识别请求到:', API_URL); const response = await fetch(API_URL, { method: 'GET', headers: { 'Authorization': `Bearer ${captchaApiKey}` } }); console.log('收到API响应,状态:', response.status); const result = await response.json(); console.log('API响应数据:', result); if (result.code === 200 && result.data && result.data.result) { // 提取所有识别的文本并拼接 const texts = result.data.result.map(item => item.text).join(''); console.log('识别成功,验证码文本:', texts); return texts; } else { console.error('识别失败:', result.msg || '未知错误'); throw new Error('识别失败: ' + (result.msg || '未知错误')); } } catch (error) { // 可能是服务器欠费,提醒用户检查API密钥和账户状态 showToast('验证码识别服务可能存在问题,请检查是否欠费或API密钥是否正确'); console.error('验证码识别错误:', error); // 如果识别失败,返回空字符串,让用户手动输入 return ''; } } // 辅助函数:延时 function sleep(ms) { console.log(`等待 ${ms} 毫秒...`); return new Promise(resolve => setTimeout(resolve, ms)); } // 辅助函数:通过XPath获取元素,支持iframe function getElementByXPath(xpath, context = document) { try { return context.evaluate(xpath, context, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; } catch (e) { console.error("XPath获取错误:", xpath, e); return null; } } // 辅助函数:等待XPath元素出现,支持iframe async function waitForElementByXPath(xpath, timeout = 5000, context = document) { console.log('等待元素出现,XPath:', xpath); const startTime = Date.now(); while (Date.now() - startTime < timeout) { const element = getElementByXPath(xpath, context); if (element) return element; await sleep(100); } console.error('等待元素超时:', xpath); return null; } // ==================== UI界面 ==================== // 创建配置界面 function createConfigUI() { const overlay = document.createElement('div'); overlay.id = 'auto-login-config-overlay'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); z-index: 10000; display: flex; justify-content: center; align-items: center; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; `; const panel = document.createElement('div'); panel.style.cssText = ` background: #ffffff; padding: 30px; border-radius: 12px; max-width: 550px; width: 90%; max-height: 85vh; overflow-y: auto; box-shadow: 0 10px 30px rgba(0,0,0,0.3); border: 1px solid #e0e0e0; `; // 使用模板字符串构建HTML,增强可读性和维护性,显示网页标题和作者信息,验证码API密钥和点此获取在同一行 panel.innerHTML = `

${scriptName} v${version}

当前网站: ${document.title}


脚本开关



作者: ${author}

`; overlay.appendChild(panel); document.body.appendChild(overlay); // 获取所有开关元素 const enabledToggle = document.getElementById('enabled-toggle'); const autoCaptchaToggle = document.getElementById('auto-captcha-toggle'); const autoLoginToggle = document.getElementById('auto-login-toggle'); // 总开关:关闭时 → 强制关闭两个子开关 enabledToggle.addEventListener('change', () => { if (!enabledToggle.checked) { autoCaptchaToggle.checked = false; autoLoginToggle.checked = false; } }); // 统一处理两个子开关的逻辑 function handleSubToggleChange() { const hasAnySubEnabled = autoCaptchaToggle.checked || autoLoginToggle.checked; if (hasAnySubEnabled) { // 任意子开关开启 → 自动开启总开关 enabledToggle.checked = true; } else { // 所有子开关关闭 → 自动关闭总开关 enabledToggle.checked = false; } } // 给两个子开关绑定同一个处理函数 autoCaptchaToggle.addEventListener('change', handleSubToggleChange); autoLoginToggle.addEventListener('change', handleSubToggleChange); // 其他按钮事件 document.getElementById('add-account-btn').addEventListener('click', addAccountForm); document.getElementById('save-config-btn').addEventListener('click', saveConfig); document.getElementById('close-config-btn').addEventListener('click', () => overlay.remove()); // 加载现有账号 loadAccountsList(); // 加载开关状态 loadToggles(); } // 加载账号列表 function loadAccountsList() { const list = document.getElementById('accounts-list'); list.innerHTML = `

账号列表 (${host})

`; if (accounts.length === 0) { list.innerHTML += '

暂无账号,请添加账号

'; return; } accounts.forEach((account, index) => { const item = document.createElement('div'); item.style.cssText = ` display: flex; justify-content: space-between; align-items: center; margin: 10px 0; padding: 15px; border: 1px solid #e0e0e0; border-radius: 8px; background: #f9f9f9; transition: background 0.3s; `; item.onmouseover = () => item.style.background = '#f0f0f0'; item.onmouseout = () => item.style.background = '#f9f9f9'; item.innerHTML = `
${account.username} ${index === defaultAccountIndex ? '(默认)' : ''}
`; list.appendChild(item); }); // 绑定删除和设为默认按钮 document.querySelectorAll('.delete-account-btn').forEach(btn => { btn.addEventListener('click', (e) => { const index = parseInt(e.target.dataset.index); accounts.splice(index, 1); if (defaultAccountIndex === index) { defaultAccountIndex = -1; } else if (defaultAccountIndex > index) { defaultAccountIndex--; } saveAccounts(); loadAccountsList(); }); }); document.querySelectorAll('.set-default-btn').forEach(btn => { btn.addEventListener('click', (e) => { defaultAccountIndex = parseInt(e.target.dataset.index); saveAccounts(); loadAccountsList(); showToast('默认账号已切换!请刷新页面以应用更改。'); }); }); } // 添加账号表单 function addAccountForm() { const overlay = document.createElement('div'); overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); z-index: 10001; display: flex; justify-content: center; align-items: center; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; `; const form = document.createElement('div'); form.style.cssText = ` background: #ffffff; padding: 30px; border-radius: 12px; max-width: 400px; width: 90%; box-shadow: 0 10px 30px rgba(0,0,0,0.3); `; form.innerHTML = `

添加新账号

`; overlay.appendChild(form); document.body.appendChild(overlay); document.getElementById('confirm-add').addEventListener('click', () => { const username = document.getElementById('new-username').value.trim(); const password = document.getElementById('new-password').value.trim(); if (!username || !password) { alert('用户名和密码不能为空!'); return; } const wasEmpty = accounts.length === 0; accounts.push({ username, password }); saveAccounts(); loadAccountsList(); overlay.remove(); if (wasEmpty) { showToast('账号添加成功!请刷新页面以应用更改。'); } else { showToast('账号添加成功!'); } }); document.getElementById('cancel-add').addEventListener('click', () => overlay.remove()); } // 加载开关状态 function loadToggles() { document.getElementById('enabled-toggle').checked = CONFIG.enabled; document.getElementById('auto-captcha-toggle').checked = CONFIG.autoCaptcha; document.getElementById('auto-login-toggle').checked = CONFIG.autoLogin; document.getElementById('captcha-api-key').value = captchaApiKey; document.getElementById('upload-api-key').value = uploadApiKey; } // 保存配置 function saveConfig() { // 如果勾选了识别验证码,则必须确保两个验证码API密钥都已填写 if (document.getElementById('auto-captcha-toggle').checked && (!document.getElementById('captcha-api-key').value.trim() || !document.getElementById('upload-api-key').value.trim())) { //取消勾选自动识别验证码 document.getElementById('auto-captcha-toggle').checked = false; showToast('自动识别验证码已被取消,因为验证码API密钥不完整'); console.log('自动识别验证码已被取消,因为验证码API密钥不完整'); } CONFIG.enabled = document.getElementById('enabled-toggle').checked; CONFIG.autoCaptcha = document.getElementById('auto-captcha-toggle').checked; CONFIG.autoLogin = document.getElementById('auto-login-toggle').checked; captchaApiKey = document.getElementById('captcha-api-key').value; uploadApiKey = document.getElementById('upload-api-key').value; GM_setValue('config', CONFIG); GM_setValue('captchaApiKey', captchaApiKey); GM_setValue('uploadApiKey', uploadApiKey); } // ==================== 自动登录逻辑 ==================== // 填充账号信息 async function fillAccount(account) { console.log('开始填充账号信息'); const usernameInput = await waitForElementByXPath(CONFIG.selectors.usernameInput); // 等待iframe加载 console.log('等待密码输入框iframe加载'); const passwordIframe = await waitForElementByXPath('//iframe[@class="dsfa-password-iframe-input_input"]'); let passwordInput = null; if (passwordIframe) { await new Promise(resolve => { if (passwordIframe.contentDocument && passwordIframe.contentDocument.readyState === 'complete') { resolve(); } else { passwordIframe.onload = resolve; } }); passwordInput = passwordIframe.contentDocument.querySelector('input[type="password"]'); } console.log('填充账号:', account.username, account.password); console.log('用户名输入框:', usernameInput); console.log('密码输入框:', passwordInput); if (usernameInput) { usernameInput.value = account.username; usernameInput.dispatchEvent(new Event('input', { bubbles: true })); usernameInput.dispatchEvent(new Event('change', { bubbles: true })); console.log('用户名填充成功'); } else { console.log('用户名输入框未找到'); } if (passwordInput) { passwordInput.value = account.password; passwordInput.dispatchEvent(new Event('input', { bubbles: true })); passwordInput.dispatchEvent(new Event('change', { bubbles: true })); console.log('密码填充成功'); } else { console.log('密码输入框未找到'); } showToast('账号已填充'); console.log('账号填充完成'); } // 处理验证码 async function handleCaptcha() { console.log('开始处理验证码'); const captchaImage = await waitForElementByXPath(CONFIG.selectors.captchaImage); const captchaInput = await waitForElementByXPath(CONFIG.selectors.captchaInput); if (!captchaImage || !captchaInput) { console.log('验证码图片或输入框未找到,跳过处理'); return; } console.log('等待验证码图片加载'); await sleep(CONFIG.delays.captchaLoad); const imageUrl = captchaImage.src; if (!imageUrl) { console.log('验证码图片URL为空,跳过处理'); return; } console.log('开始识别验证码'); const code = await recognizeCaptcha(imageUrl); if (code) { captchaInput.value = code; captchaInput.dispatchEvent(new Event('input', { bubbles: true })); captchaInput.dispatchEvent(new Event('change', { bubbles: true })); showToast('验证码已自动填写'); console.log('验证码自动填写成功'); } else { showToast('验证码识别失败,请手动输入'); console.log('验证码识别失败'); } } // 显示提示消息 function showToast(message) { const toast = document.createElement('div'); toast.textContent = message; toast.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #333; color: white; padding: 15px 25px; border-radius: 8px; z-index: 10001; font-size: 16px; opacity: 0.95; text-align: center; box-shadow: 0 4px 12px rgba(0,0,0,0.3); `; document.body.appendChild(toast); console.log('显示提示消息:', message); setTimeout(() => toast.remove(), 3000); } // 主函数 async function main() { console.log('自动登录脚本主函数开始执行'); try { // 加载配置 const savedConfig = GM_getValue('config', {}); Object.assign(CONFIG, savedConfig); if (!CONFIG.enabled) { console.log('脚本被禁用,退出'); return; } // 等待页面加载 console.log('等待页面加载...'); await sleep(CONFIG.delays.pageLoad); // 自动填充默认账号 const defaultAccount = getDefaultAccount(); if (defaultAccount) { console.log('找到默认账号,开始填充'); await fillAccount(defaultAccount); // 自动处理验证码 if (CONFIG.autoCaptcha) { console.log('启用自动验证码,开始处理'); await handleCaptcha(); } // 自动点击登录按钮 if (CONFIG.autoLogin) { console.log('启用自动登录,等待后点击登录按钮'); await sleep(1500); // 等待验证码处理完成 const loginBtn = getElementByXPath(CONFIG.selectors.loginButton); if (loginBtn) { loginBtn.click(); showToast('已自动点击登录'); } else { console.log('登录按钮未找到,无法自动点击'); } } } else { console.log('无可用账号,跳过自动登录'); showToast('无可用账号,请先配置账号'); } } catch (error) { console.error('脚本执行错误:', error); showToast('脚本执行失败,请检查控制台'); } console.log('自动登录脚本主函数执行完毕'); } // 添加配置按钮到页面 function addConfigButton() { const button = document.createElement('button'); button.textContent = '⚙️ 账号配置'; button.style.cssText = ` position: fixed; top: 20px; left: 20px; z-index: 9999; background: linear-gradient(135deg, #28a745, #1e7e34); color: white; border: none; border-radius: 50px; padding: 12px 20px; font-size: 14px; font-weight: 500; cursor: pointer; box-shadow: 0 4px 12px rgba(40,167,69,0.3); transition: all 0.3s ease; display: flex; align-items: center; gap: 8px; `; button.onmouseover = () => { button.style.transform = 'scale(1.05)'; button.style.boxShadow = '0 6px 20px rgba(40,167,69,0.4)'; }; button.onmouseout = () => { button.style.transform = 'scale(1)'; button.style.boxShadow = '0 4px 12px rgba(40,167,69,0.3)'; }; button.addEventListener('click', createConfigUI); document.body.appendChild(button); } // 初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { if (window.location.href.includes('login')) { addConfigButton(); main(); } }); } else { if (window.location.href.includes('login')) { addConfigButton(); main(); } } })();