// isDomainMatch 由 utils.js 提供(全局函数) // 2FA / 验证码检测:检测到则不自动提交,仅填充 function detectCaptcha() { // 1. reCAPTCHA if (document.querySelector(".g-recaptcha, [data-sitekey], iframe[src*='recaptcha'], iframe[src*='google.com/recaptcha']")) { return { type: "reCAPTCHA", reason: "检测到 reCAPTCHA" }; } // 2. hCaptcha if (document.querySelector(".h-captcha, iframe[src*='hcaptcha']")) { return { type: "hCaptcha", reason: "检测到 hCaptcha" }; } // 3. 国内常见滑块/验证码容器 const sliderSelectors = [ "#slider", ".slider", ".nc_wrapper", ".nc_iconfont", "#captcha", ".captcha", ".geetest", "#geetest", "[class*='slider']", "[id*='slider']", "[id*='captcha']", "[class*='captcha']", "canvas[id*='captcha']", "canvas[id*='verify']" ]; for (const sel of sliderSelectors) { try { const el = document.querySelector(sel); if (el && el.offsetParent !== null) { return { type: "slider/captcha", reason: `检测到验证码元素 (${sel})` }; } } catch { /* selector 可能无效 */ } } // 4. 明显的"验证码"文字输入框(带图形码) const codeInputs = document.querySelectorAll( "input[name*='captcha' i], input[name*='verify' i], input[name*='code' i], input[id*='captcha' i], input[id*='verify' i], input[placeholder*='验证码' i], input[placeholder*='验证' i]" ); for (const input of codeInputs) { if (input.offsetParent !== null && input.type !== "hidden") { return { type: "captcha-input", reason: "检测到验证码输入框" }; } } return null; } function setNativeValue(el, value) { const nativeInputValueSetter = Object.getOwnPropertyDescriptor( window.HTMLInputElement.prototype, "value" )?.set || Object.getOwnPropertyDescriptor( window.HTMLTextAreaElement.prototype, "value" )?.set; if (nativeInputValueSetter) { nativeInputValueSetter.call(el, value); } else { el.value = value; } el.dispatchEvent(new Event("input", { bubbles: true })); el.dispatchEvent(new Event("change", { bubbles: true })); } function findUsernameField(config) { if (config.usernameSelector) { const el = document.querySelector(config.usernameSelector); if (el) return el; } const passwordField = findPasswordField(config); if (!passwordField) return null; const form = passwordField.closest("form"); const searchScope = form || document; const selectors = [ "input[type='text']", "input[type='email']", "input[type='tel']", "input:not([type='password']):not([type='hidden']):not([type='submit']):not([type='button']):not([type='checkbox']):not([type='radio'])", ]; for (const sel of selectors) { const inputs = searchScope.querySelectorAll(sel); for (const input of inputs) { if (input.offsetParent !== null || input.offsetWidth > 0) { const name = (input.name || "").toLowerCase(); const id = (input.id || "").toLowerCase(); const placeholder = (input.placeholder || "").toLowerCase(); const autocomplete = (input.getAttribute("autocomplete") || "").toLowerCase(); if ( name.includes("user") || name.includes("name") || name.includes("account") || name.includes("email") || name.includes("login") || name.includes("mail") || id.includes("user") || id.includes("name") || id.includes("account") || id.includes("email") || id.includes("login") || placeholder.includes("user") || placeholder.includes("name") || placeholder.includes("email") || placeholder.includes("account") || placeholder.includes("登录") || placeholder.includes("账号") || placeholder.includes("用户") || placeholder.includes("邮箱") || autocomplete.includes("username") || autocomplete.includes("email") ) { return input; } } } } const allInputs = searchScope.querySelectorAll( "input[type='text'], input[type='email'], input:not([type='password']):not([type='hidden']):not([type='submit']):not([type='button']):not([type='checkbox']):not([type='radio'])" ); for (const input of allInputs) { if (input.offsetParent !== null || input.offsetWidth > 0) { return input; } } return null; } function findPasswordField(config) { if (config.passwordSelector) { const el = document.querySelector(config.passwordSelector); if (el) return el; } const passwordInputs = document.querySelectorAll("input[type='password']"); for (const input of passwordInputs) { if (input.offsetParent !== null || input.offsetWidth > 0) { return input; } } return passwordInputs.length > 0 ? passwordInputs[0] : null; } function findSubmitButton(config, passwordField) { if (config.submitSelector) { const el = document.querySelector(config.submitSelector); if (el) return el; } const form = passwordField ? passwordField.closest("form") : null; const searchScope = form || document; const submitSelectors = [ "button[type='submit']", "input[type='submit']", "button[type='button']", "input[type='button']", ]; for (const sel of submitSelectors) { const btns = searchScope.querySelectorAll(sel); for (const btn of btns) { if (btn.offsetParent !== null || btn.offsetWidth > 0) { const text = (btn.textContent || btn.value || "").toLowerCase().trim(); if ( text.includes("登录") || text.includes("login") || text.includes("log in") || text.includes("sign in") || text.includes("提交") || text.includes("submit") || text.includes("确定") || text.includes("ok") || text.includes("go") ) { return btn; } } } } for (const sel of submitSelectors) { const btn = searchScope.querySelector(sel); if (btn && (btn.offsetParent !== null || btn.offsetWidth > 0)) { return btn; } } const allButtons = searchScope.querySelectorAll("button, input[type='button'], a[role='button']"); for (const btn of allButtons) { if (btn.offsetParent !== null || btn.offsetWidth > 0) { const text = (btn.textContent || btn.value || "").toLowerCase().trim(); if ( text.includes("登录") || text.includes("login") || text.includes("log in") || text.includes("sign in") ) { return btn; } } } return null; } function fillForm(config, canSubmit = true) { const passwordField = findPasswordField(config); if (!passwordField) { console.log("[AutoLogin] 未找到密码输入框"); return false; } const usernameField = findUsernameField(config); if (usernameField) { setNativeValue(usernameField, config.username); console.log("[AutoLogin] 已填充用户名"); } else { console.log("[AutoLogin] 未找到用户名输入框,跳过用户名填充"); } setNativeValue(passwordField, config.password); console.log("[AutoLogin] 已填充密码"); // 验证码检测:检测到则强制不自动提交,提示用户手动操作 const captcha = detectCaptcha(); let actualCanSubmit = canSubmit; if (captcha && config.autoSubmit) { actualCanSubmit = false; console.warn(`[AutoLogin] ${captcha.reason},已跳过自动提交,请手动完成验证码后点击登录`); // 在页面右上角显示提示浮窗 showCaptchaToast(captcha.reason); } if (config.autoSubmit && actualCanSubmit) { const submitBtn = findSubmitButton(config, passwordField); if (submitBtn) { // 延迟提交:config.delayMs 给某些需要 JS 初始化的页面留时间 const delay = Math.max(300, Number(config.delayMs) || 300); setTimeout(() => { console.log(`[AutoLogin] 自动点击登录按钮(延迟 ${delay}ms)`); submitBtn.click(); }, delay); return true; } if (passwordField.form) { const delay = Math.max(300, Number(config.delayMs) || 300); console.log("[AutoLogin] 未找到登录按钮,尝试提交表单"); setTimeout(() => { passwordField.form.requestSubmit ? passwordField.form.requestSubmit() : passwordField.form.submit(); }, delay); return true; } console.log("[AutoLogin] 未找到登录按钮,也无法提交表单"); } else if (config.autoSubmit && !actualCanSubmit && captcha) { console.warn("[AutoLogin] 已检测到验证码,仅填充不自动提交,请手动完成验证码后点击登录"); } else if (config.autoSubmit && !canSubmit) { console.warn("[AutoLogin] 自动登录失败次数已达上限,仅填充不自动提交,请手动确认后点击登录"); } else { console.log("[AutoLogin] 自动提交已关闭,仅填充"); } return true; } // 验证码提示浮窗:在页面右上角显示,几秒后自动消失 let captchaToastEl = null; function showCaptchaToast(reason) { if (captchaToastEl && captchaToastEl.parentNode) { captchaToastEl.parentNode.removeChild(captchaToastEl); } const el = document.createElement("div"); el.style.cssText = ` position: fixed; top: 16px; right: 16px; z-index: 2147483647; background: #1a1a2e; border: 1px solid #e94560; border-radius: 8px; padding: 10px 14px; color: #e0e0e0; font-size: 13px; font-family: "Segoe UI","Microsoft YaHei",sans-serif; box-shadow: 0 4px 16px rgba(0,0,0,0.4); max-width: 300px; `; el.innerHTML = `