const t = (key, ...args) => chrome.i18n.getMessage(key, args) || key; async function safeStorageGet(area, keys) { try { return await chrome.storage[area].get(keys); } catch { return {}; } } async function safeStorageSet(area, items) { try { await chrome.storage[area].set(items); } catch {} } // 2FA / 验证码检测:检测到则不自动提交,仅填充 function detectCaptcha() { // reCAPTCHA if (document.querySelector(".g-recaptcha, [data-sitekey], iframe[src*='recaptcha'], iframe[src*='google.com/recaptcha']")) { return { type: "reCAPTCHA", reason: t("captchaTitleRe") }; } // hCaptcha if (document.querySelector(".h-captcha, iframe[src*='hcaptcha']")) { return { type: "hCaptcha", reason: t("captchaTitleH") }; } // 国内常见滑块/验证码容器 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: t("captchaReasonSlider", sel) }; } } catch { /* selector 可能无效 */ } } // 明显的"验证码"文字输入框(带图形码) 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: t("captchaReasonInput") }; } } 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) { let searchScope = config.usernameSelector ? document.querySelector(config.usernameSelector) : null; if (!searchScope) { const passwordField = findPasswordField(config); if (!passwordField) return null; searchScope = passwordField.closest("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) { const scope = config.passwordSelector ? (document.querySelector(config.passwordSelector) || document) : document; const passwordInputs = scope.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) { let searchScope = config.submitSelector ? document.querySelector(config.submitSelector) : null; if (!searchScope) { const form = passwordField ? passwordField.closest("form") : null; 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(); const compact = text.replace(/\s/g, ""); if ( compact.includes("登录") || compact.includes("login") || compact.includes("login") || compact.includes("signin") || compact.includes("提交") || compact.includes("submit") || compact.includes("确定") || compact.includes("ok") || compact.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(); const compact = text.replace(/\s/g, ""); if ( compact.includes("登录") || compact.includes("login") || compact.includes("signin") ) { return btn; } } } return null; } function fillForm(config, canSubmit = true) { const passwordField = findPasswordField(config); if (!passwordField) { console.log("[AutoLogin]", t("logNoPasswordField")); return false; } const usernameField = findUsernameField(config); if (usernameField) { setNativeValue(usernameField, config.username); console.log("[AutoLogin]", t("logFilledUsername")); } else { console.log("[AutoLogin]", t("logNoUsernameField")); } setNativeValue(passwordField, config.password); console.log("[AutoLogin]", t("logFilledPassword")); // 检测到验证码则强制不自动提交,提示用户手动操作 const captcha = detectCaptcha(); let actualCanSubmit = canSubmit; if (captcha && config.autoSubmit) { actualCanSubmit = false; console.warn("[AutoLogin]", t("logCaptchaSkip", captcha.reason)); showCaptchaToast(captcha.reason); } if (config.autoSubmit && actualCanSubmit) { const submitBtn = findSubmitButton(config, passwordField); if (submitBtn) { // delayMs 给需要 JS 初始化的页面留出时间 const delay = Math.max(300, Number(config.delayMs) || 300); setTimeout(() => { console.log("[AutoLogin]", t("logAutoClick", String(delay))); submitBtn.click(); }, delay); return true; } if (passwordField.form) { const delay = Math.max(300, Number(config.delayMs) || 300); console.log("[AutoLogin]", t("logTryFormSubmit")); setTimeout(() => { passwordField.form.requestSubmit ? passwordField.form.requestSubmit() : passwordField.form.submit(); }, delay); return true; } console.log("[AutoLogin]", t("logNoSubmitAndForm")); } else if (config.autoSubmit && !actualCanSubmit && captcha) { console.warn("[AutoLogin]", t("logCaptchaFilled")); } else if (config.autoSubmit && !canSubmit) { console.warn("[AutoLogin]", t("logAttemptExceeded")); } else { console.log("[AutoLogin]", t("logNoAutoSubmit")); } return true; } // 验证码提示浮窗,8 秒后自动消失 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 = `