// ==UserScript== // @name 统计云自动登陆 // @namespace http://tampermonkey.net/ // @version 3.4 // @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-16 v3.4 // - 修改了ocr api为ocr.space,并内置了一个密钥:helloworld // 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; // 默认ocr api密钥 const DEFAULT_OCR_API_KEY = 'helloworld'; 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); // ocr API 密钥 let ocrApiKey = GM_getValue('ocrApiKey', ''); console.log('加载ocr API密钥:', { ocrApiKey: !!ocrApiKey }); // 如果没有提供密钥,使用默认密钥 if (!ocrApiKey || ocrApiKey.trim() === "") { ocrApiKey = DEFAULT_OCR_API_KEY; console.log('未提供ocr API密钥,使用默认密钥:', DEFAULT_OCR_API_KEY); } // 获取默认账号 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; } // ocr接口 async function ocrBase64(apiKey, base64) { try { const res = await fetch("https://api.ocr.space/parse/image", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: "apikey=" + apiKey + "&base64Image=" + encodeURIComponent(base64) }); const data = await res.json(); if (data.IsErroredOnProcessing) { console.error("ocr识别失败:", data.ErrorMessage); return; } console.log("ocr识别结果:", data.ParsedResults[0]?.ParsedText); return data.ParsedResults[0]?.ParsedText; } catch (err) { console.error("ocr请求错误:", err); } } // 显示提示消息(支持多个消息堆叠) const activeToasts = []; function showToast(message, arg) { const toast = document.createElement('div'); toast.textContent = message; toast.style.cssText = ` position: fixed; left: 50%; transform: translateX(-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); transition: top 0.3s ease; `; // 计算位置:第一个在屏幕中央,后续的依次向下排列 const toastHeight = 60; // 估算每个toast的高度(包括间距) const index = activeToasts.length; const topPosition = 50 + (index * toastHeight); toast.style.top = `calc(${topPosition}px + 20%)`; document.body.appendChild(toast); activeToasts.push(toast); console.log(message, arg); setTimeout(() => { toast.remove(); const idx = activeToasts.indexOf(toast); if (idx > -1) { activeToasts.splice(idx, 1); // 重新调整剩余toast的位置 activeToasts.forEach((t, i) => { const newTop = 50 + (i * toastHeight); t.style.top = `calc(${newTop}px + 20%)`; }); } }, 3000); } // 保存账号数据(按 host 区分) function saveAccounts() { GM_setValue('accounts_' + host, accounts); GM_setValue('defaultAccountIndex_' + host, defaultAccountIndex); console.log('账号数据已保存', accounts, '默认账号索引:', defaultAccountIndex); } // 辅助函数:延时 function sleep(ms) { console.log(`等待 ${ms} 毫秒...`); return new Promise(resolve => setTimeout(resolve, ms)); } // 辅助函数:通过XPath获取元素,支持iframe function getElementByXPath(xpath, context = document) { try { console.log('正在通过XPath获取元素:', xpath); 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; color: #333; `; // 使用模板字符串构建HTML,增强可读性和维护性,显示网页标题和作者信息 panel.innerHTML = `

${scriptName} v${version}

当前网站: ${document.title}


脚本开关



`; 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); color: #333; `; 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) { showToast('用户名和密码不能为空!'); return; } accounts.push({ username, password }); saveAccounts(); loadAccountsList(); overlay.remove(); showToast('账号添加成功!'); }); document.getElementById('cancel-add').addEventListener('click', () => overlay.remove()); } // 加载开关状态 function loadToggles() { console.log('加载开关状态'); 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('ocr-api-key').value = ocrApiKey; } // 保存配置 function saveConfig() { console.log('开始保存配置'); // 如果勾选了OCR识别,则检查OCR API密钥已填写 if (document.getElementById('auto-captcha-toggle').checked && !document.getElementById('ocr-api-key').value.trim()) { showToast('未填写OCR API密钥,将使用默认密钥'); // 使用默认密钥 ocrApiKey = DEFAULT_OCR_API_KEY; } else { // 否则使用用户输入的密钥 ocrApiKey = document.getElementById('ocr-api-key').value; } CONFIG.enabled = document.getElementById('enabled-toggle').checked; CONFIG.autoCaptcha = document.getElementById('auto-captcha-toggle').checked; CONFIG.autoLogin = document.getElementById('auto-login-toggle').checked; GM_setValue('config', CONFIG); GM_setValue('ocrApiKey', ocrApiKey); showToast('配置已保存', CONFIG); } // ==================== 自动登录逻辑 ==================== // 填充账号信息 async function fillAccount(account) { console.log('开始填充账号信息', account); 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 ocrBase64(ocrApiKey, imageUrl); if (code) { captchaInput.value = code; captchaInput.dispatchEvent(new Event('input', { bubbles: true })); captchaInput.dispatchEvent(new Event('change', { bubbles: true })); showToast('验证码已自动填写', code); } else { showToast("ocr识别失败,如多次失败请检查ocr API密钥或者关闭自动验证码功能", code); } } // 主函数 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('启用自动登录,等待后点击登录按钮'); const loginBtn = getElementByXPath(CONFIG.selectors.loginButton); if (loginBtn) { loginBtn.click(); showToast('已自动点击登录'); } else { console.log('登录按钮未找到,无法自动点击'); } } } else { showToast('无可用账号,请先配置账号'); } } catch (error) { showToast('脚本执行失败,请检查控制台', error); } 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(); } } })();