// ==UserScript== // @name 美国大兵 GPT 认证 // @namespace http://tampermonkey.net/ // @version 16.0 // @description 全流程全自动:海事局抓取 -> ChatGPT 任务分发 -> SheerID 自动填表 -> Outlook 自动点击验证 (统一状态控制) // @author Antigravity // @match https://services.sheerid.com/* // @match https://gravelocator.cem.va.gov/* // @match https://chatgpt.com/veterans-claim* // @match https://outlook.live.com/* // @match https://outlook.office.com/* // @match https://outlook.office365.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_addStyle // @grant GM_openInTab // @grant GM_registerMenuCommand // @license MIT // @downloadURL none // ==/UserScript== (function () { 'use strict'; // --- 强力后台保活 (Web Worker Hack) --- function setWorkerInterval(callback, delay) { const blob = new Blob([`setInterval(() => postMessage('tick'), ${delay});`], { type: 'text/javascript' }); const worker = new Worker(URL.createObjectURL(blob)); worker.onmessage = callback; return worker; } // --- 核心配置 --- const FIELD_MAP = { status: '#sid-military-status', branch: '#sid-branch-of-service', firstName: '#sid-first-name', lastName: '#sid-last-name', bMonth: '#sid-birthdate__month', bDay: '#sid-birthdate-day', bYear: '#sid-birthdate-year', dMonth: '#sid-discharge-date__month', dDay: '#sid-discharge-date-day', dYear: '#sid-discharge-date-year', email: '#sid-email' }; const SUBMIT_BTN_SELECTOR = '#sid-submit-btn-collect-info'; const CHATGPT_CLAIM_TEXTS = ['Claim Now', 'Verify eligibility']; const SHEERID_RETRY_TEXTS = ['Try again', 'Retry', 'Try Again']; // 🔥 可配置项 (从存储读取,带默认值) function getConfig() { return { FIXED_STATUS: GM_getValue('config_fixed_status', "Military Veteran or Retiree"), FIXED_DISCHARGE_YEAR: GM_getValue('config_discharge_year', "2025"), FIXED_EMAIL: GM_getValue('config_email', "your-email@example.com"), MIN_BIRTH_YEAR: GM_getValue('config_min_birth_year', 1930), SENDER_FILTER: GM_getValue('config_sender_filter', "SheerID") }; } function saveConfig(config) { GM_setValue('config_fixed_status', config.FIXED_STATUS); GM_setValue('config_discharge_year', config.FIXED_DISCHARGE_YEAR); GM_setValue('config_email', config.FIXED_EMAIL); GM_setValue('config_min_birth_year', config.MIN_BIRTH_YEAR); GM_setValue('config_sender_filter', config.SENDER_FILTER); } const MONTH_MAP = { "01": "January", "02": "February", "03": "March", "04": "April", "05": "May", "06": "June", "07": "July", "08": "August", "09": "September", "10": "October", "11": "November", "12": "December" }; // 🔥 Outlook 专属配置 (senderFilter 从动态配置读取) function getOutlookConfig() { return { senderFilter: getConfig().SENDER_FILTER, linkKeywords: ['verify', 'confirm', 'complete', '验证', '点击'], checkInterval: 5000, autoClick: true, maxRetries: 3 }; } let outlookLogBuffer = []; // --- 状态管理 --- function getQueue() { return GM_getValue('global_auth_queue', []); } function saveQueue(arr) { GM_setValue('global_auth_queue', arr); updateUI(); } function getCurrentTask() { return GM_getValue('current_active_task', null); } function setCurrentTask(task) { GM_setValue('current_active_task', task); } function getIsRunning() { return GM_getValue('is_script_running', false); } function setIsRunning(bool) { GM_setValue('is_script_running', bool); updateUI(); } function getTaskStage() { return GM_getValue('current_task_stage', 'IDLE'); } function setTaskStage(stage) { GM_setValue('current_task_stage', stage); } // --- Outlook 专用工具函数 --- function logOutlook(message, type = 'info') { const timestamp = new Date().toLocaleTimeString(); const logEntry = `[${timestamp}] [${type.toUpperCase()}] ${message}`; console.log(logEntry); outlookLogBuffer.push(logEntry); if (outlookLogBuffer.length > 50) outlookLogBuffer.shift(); updateOutlookUI(); } function getProcessedHistory() { return GM_getValue('processed_history_ids', []); } function addToHistory(id) { let history = getProcessedHistory(); if (!history.includes(id)) { history.push(id); if (history.length > 50) history.shift(); GM_setValue('processed_history_ids', history); } } // --- Outlook 核心业务逻辑 --- async function processOutlookEmails() { if (!getIsRunning()) return; try { // 互斥检查已移除 (V17.5 独立守护进程架构):Outlook 将持续扫描未读邮件,不再依赖 T1 状态 const emailItems = document.querySelectorAll('div[role="option"], div[data-convid]'); const history = getProcessedHistory(); for (let idx = 0; idx < emailItems.length; idx++) { const item = emailItems[idx]; const fullAria = (item.getAttribute('aria-label') || ""); const ariaLabel = fullAria.toLowerCase(); const isUnread = ariaLabel.includes('unread') || ariaLabel.includes('未读'); const isSelected = item.getAttribute('aria-selected') === 'true'; if (!isUnread || !ariaLabel.includes(getOutlookConfig().senderFilter.toLowerCase()) || isSelected) continue; // 使用 data-convid 作为唯一 ID (如果存在),否则用增强指纹 const convId = item.getAttribute('data-convid') || ''; const stableFingerprint = fullAria.replace(/^(未读|unread|已读|read)\s*/i, '').substring(0, 120).replace(/[^a-zA-Z0-9]/g, ''); // 优先使用 convId,它是 Outlook 的唯一会话 ID const emailId = convId ? `conv_${convId}` : `mail_${stableFingerprint}_i${idx}`; if (history.includes(emailId)) continue; logOutlook(`📨 Detect New Mail (ID:${emailId.substring(0, 20)}...)`, 'success'); simulateClick(item); addToHistory(emailId); let finalLink = null; for (let i = 0; i < 20; i++) { await new Promise(r => setTimeout(r, 500)); const readingPane = document.querySelector('div[role="document"], #ReadingPaneContainerId'); if (readingPane) { const found = Array.from(readingPane.querySelectorAll('a')).find(a => a.href.toLowerCase().includes('sheerid.com/verify') || (getOutlookConfig().linkKeywords.some(kw => a.innerText.toLowerCase().includes(kw)) && a.href.includes('sheerid')) ); const regexMatch = readingPane.innerHTML.match(/https?:\/\/services\.sheerid\.com\/verify\/[a-zA-Z0-9_-]+/); finalLink = found?.href || regexMatch?.[0]; if (finalLink) break; } } if (finalLink && getOutlookConfig().autoClick) { const cleanLink = finalLink.trim(); logOutlook(`🔗 Opening Verify Link...`, 'action'); // 增加微小延迟,确保在重负载下标签页开启指令能被浏览器正确接收 setTimeout(() => { GM_openInTab(cleanLink, { active: true, insert: true, setParent: true }); }, 100); return; } else if (!finalLink) { logOutlook('❌ Timeout: No link found', 'error'); } break; } } catch (error) { logOutlook(`System Error: ${error.message}`, 'error'); } } function runOutlookDiagnostics() { logOutlook("🔍 Running Enhanced Diagnostics...", "action"); // 1. 检查邮件项 const items = document.querySelectorAll('div[role="option"], div[data-convid]'); logOutlook(`找到 ${items.length} 个邮件项`, 'info'); // 2. 输出前5个邮件项的详细信息 Array.from(items).slice(0, 5).forEach((el, i) => { const aria = el.getAttribute('aria-label') || ''; const isSheerID = aria.toLowerCase().includes('sheerid'); const isUnread = aria.toLowerCase().includes('unread') || aria.toLowerCase().includes('未读'); logOutlook(`--- 邮件 ${i + 1} ${isSheerID ? '✅SheerID' : ''} ${isUnread ? '📩未读' : ''} ---`, 'debug'); logOutlook(`aria(前100): ${aria.substring(0, 100)}`, 'debug'); logOutlook(`data-convid: ${el.getAttribute('data-convid') || '无'}`, 'debug'); // 输出所有 data-* 属性 const dataAttrs = Array.from(el.attributes) .filter(a => a.name.startsWith('data-')) .map(a => `${a.name}=${a.value.substring(0, 30)}`); if (dataAttrs.length > 0) { logOutlook(`data-*: ${dataAttrs.join(', ')}`, 'debug'); } }); // 3. 统计 SheerID 未读邮件 const sheerIdUnread = Array.from(items).filter(el => { const aria = (el.getAttribute('aria-label') || '').toLowerCase(); return aria.includes('sheerid') && (aria.includes('unread') || aria.includes('未读')); }); logOutlook(`SheerID 未读邮件数: ${sheerIdUnread.length}`, 'info'); // 4. 显示已处理历史 const history = getProcessedHistory(); logOutlook(`已处理历史: ${history.length} 条`, 'info'); if (history.length > 0) { logOutlook(`最近3条: ${history.slice(-3).join(' | ')}`, 'debug'); } } function updateOutlookUI() { const container = document.getElementById('outlook-log-container'); if (!container) return; container.innerHTML = outlookLogBuffer.map(msg => { let className = 'log-entry'; if (msg.includes('[SUCCESS]')) className += ' log-success'; if (msg.includes('[WARN]')) className += ' log-warn'; if (msg.includes('[ERROR]')) className += ' log-error'; if (msg.includes('[ACTION]')) className += ' log-action'; return `
${msg}
`; }).join(''); container.scrollTop = container.scrollHeight; } function createOutlookPanel() { if (document.getElementById('outlook-assistant-panel')) return; const panel = document.createElement('div'); panel.id = 'outlook-assistant-panel'; panel.innerHTML = `

📧 Outlook 联动窗 (V16.0)

共享状态: ...
`; document.body.appendChild(panel); GM_addStyle(` #outlook-assistant-panel { position: fixed; top: 10px; right: 20px; width: 280px; background: rgba(30,30,30,0.9); color: #fff; border-radius: 8px; z-index: 999999; padding: 10px; font-family: sans-serif; box-shadow: 0 4px 15px rgba(0,0,0,0.5); border: 1px solid #444; backdrop-filter: blur(5px); } .outlook-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #444; padding-bottom: 5px; margin-bottom: 8px; } .outlook-header h3 { margin: 0; font-size: 13px; color: #0078d4; } #outlook-log-container .log-entry { margin-bottom: 2px; border-bottom: 1px solid rgba(255,255,255,0.05); } #outlook-log-container .log-success { color: #4ec9b0; } #outlook-log-container .log-error { color: #f44747; } #outlook-log-container .log-action { color: #3794ff; } `); document.getElementById('btn-outlook-diag').onclick = runOutlookDiagnostics; document.getElementById('btn-outlook-copy').onclick = () => { const text = outlookLogBuffer.join('\n'); navigator.clipboard.writeText(text).then(() => { alert('已复制到剪贴板!'); }).catch(() => { // Fallback: 输出到 console console.log('=== Outlook 诊断日志 ===\n' + text); alert('复制失败,请打开控制台查看 (F12)'); }); }; document.getElementById('btn-outlook-clear').onclick = () => { if (confirm('确定清空邮件处理历史?这会导致已处理的邮件被重新检测。')) { GM_setValue('processed_history_ids', []); logOutlook('✅ 历史记录已清空', 'success'); } }; document.getElementById('close-outlook').onclick = () => panel.style.display = 'none'; const toggleBtn = document.getElementById('btn-outlook-toggle'); const statusText = document.getElementById('outlook-status-text'); function syncOutlookUI() { const running = getIsRunning(); toggleBtn.innerText = running ? "⏸️ 停止助手" : "▶️ 启动助手"; toggleBtn.style.background = running ? "#d83b01" : "#0078d4"; statusText.innerText = running ? "正在监听任务..." : "已停止"; statusText.style.color = running ? "#4ec9b0" : "#f44747"; } toggleBtn.onclick = () => { const newState = !getIsRunning(); setIsRunning(newState); syncOutlookUI(); if (newState) logOutlook("助手已启动,监听 AWAITING_EMAIL 信号...", "info"); }; syncOutlookUI(); setInterval(syncOutlookUI, 2000); // 跨标签页同步状态 logOutlook("Outlook 联动模块已就绪"); updateOutlookUI(); } // --- UI 创建 --- function createPanel() { if (document.getElementById('auth_helper_panel')) return; const div = document.createElement('div'); div.id = 'auth_helper_panel'; div.style.cssText = "position: fixed; bottom: 50px; right: 20px; width: 360px; background: #fff; border: 2px solid #6610f2; box-shadow: 0 5px 25px rgba(0,0,0,0.3); z-index: 999999; padding: 15px; border-radius: 8px; font-family: sans-serif; font-size: 13px;"; const header = document.createElement('div'); header.style.cssText = "font-weight:bold; color:#6610f2; margin-bottom:10px; border-bottom:1px solid #ddd; padding-bottom:10px; display:flex; justify-content:space-between; align-items:center;"; const title = document.createElement('span'); title.style.fontSize = "14px"; title.textContent = "🚀 认证助手 V16.0 (统一版)"; const count = document.createElement('span'); count.id = "queue_count"; count.style.cssText = "background:#dc3545; color:white; padding:4px 12px; border-radius:20px; font-size:18px; font-weight:bold;"; count.textContent = "0"; header.appendChild(title); header.appendChild(count); div.appendChild(header); const statusArea = document.createElement('div'); statusArea.id = "status_area"; statusArea.style.cssText = "margin-bottom: 10px; color: #333; min-height: 20px; font-weight:bold;"; statusArea.textContent = "待命中..."; div.appendChild(statusArea); const btnRow = document.createElement('div'); btnRow.style.cssText = "display:flex; gap:8px; margin-bottom: 10px;"; const btnToggle = document.createElement('button'); btnToggle.id = "btn_toggle"; btnToggle.style.cssText = "flex:2; padding: 12px; border: none; border-radius: 4px; font-weight: bold; font-size: 15px; cursor: pointer; color: white;"; const btnSkip = document.createElement('button'); btnSkip.id = "btn_skip"; btnSkip.style.cssText = "flex:1; padding: 12px; background: #ffc107; color: #000; border: none; border-radius: 4px; font-weight: bold; font-size: 13px; cursor: pointer;"; btnSkip.textContent = "⏭️ 跳过"; btnRow.appendChild(btnToggle); btnRow.appendChild(btnSkip); div.appendChild(btnRow); const importSection = document.createElement('div'); const textarea = document.createElement('textarea'); textarea.id = "bulk_input"; textarea.placeholder = "粘贴数据或抓取数据..."; textarea.style.cssText = "width: 100%; height: 60px; margin-bottom: 5px; font-size:12px; border:1px solid #ccc; padding:5px;"; const subBtnRow = document.createElement('div'); subBtnRow.style.cssText = "display:flex; gap:5px;"; const btnScrape = document.createElement('button'); btnScrape.id = "btn_scrape"; btnScrape.style.cssText = "flex:1.5; padding: 8px; cursor: pointer; background:#198754; color:white; border:none; border-radius:4px; display:none;"; btnScrape.textContent = "📥 抓取本页"; if (location.host.includes('gravelocator.cem.va.gov')) btnScrape.style.display = 'block'; const btnImport = document.createElement('button'); btnImport.id = "btn_import"; btnImport.style.cssText = "flex:1; padding: 8px; cursor: pointer; background:#0d6efd; color:white; border:none; border-radius:4px;"; btnImport.textContent = "📥 存入"; const btnReset = document.createElement('button'); btnReset.id = "btn_reset"; btnReset.style.cssText = "flex:1; padding: 8px; cursor: pointer; background:#dc3545; color:white; border:none; border-radius:4px;"; btnReset.textContent = "🗑️ 清空状态"; subBtnRow.appendChild(btnScrape); subBtnRow.appendChild(btnImport); subBtnRow.appendChild(btnReset); importSection.appendChild(textarea); importSection.appendChild(subBtnRow); div.appendChild(importSection); // === 配置面板 === const configSection = document.createElement('div'); configSection.id = "config_section"; configSection.style.cssText = "margin-top: 10px; border-top: 1px solid #ddd; padding-top: 10px;"; const configToggle = document.createElement('button'); configToggle.id = "btn_config_toggle"; configToggle.style.cssText = "width: 100%; padding: 6px; background: #6c757d; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; margin-bottom: 8px;"; configToggle.textContent = "⚙️ 显示配置"; configSection.appendChild(configToggle); const configPanel = document.createElement('div'); configPanel.id = "config_panel"; configPanel.style.cssText = "display: none; font-size: 11px;"; const cfg = getConfig(); const configFields = [ { id: 'cfg_email', label: '📧 邮箱', value: cfg.FIXED_EMAIL, key: 'FIXED_EMAIL' }, { id: 'cfg_status', label: '🎖️ 身份', value: cfg.FIXED_STATUS, key: 'FIXED_STATUS' }, { id: 'cfg_discharge_year', label: '📅 退役年', value: cfg.FIXED_DISCHARGE_YEAR, key: 'FIXED_DISCHARGE_YEAR' }, { id: 'cfg_min_birth_year', label: '🎂 最小出生年', value: cfg.MIN_BIRTH_YEAR, key: 'MIN_BIRTH_YEAR', type: 'number' }, { id: 'cfg_sender_filter', label: '📬 发件人过滤', value: cfg.SENDER_FILTER, key: 'SENDER_FILTER' } ]; configFields.forEach(field => { const row = document.createElement('div'); row.style.cssText = "display: flex; align-items: center; margin-bottom: 5px;"; const label = document.createElement('label'); label.style.cssText = "flex: 0 0 90px; font-size: 11px;"; label.textContent = field.label; const input = document.createElement('input'); input.id = field.id; input.type = field.type || 'text'; input.value = field.value; input.dataset.key = field.key; input.style.cssText = "flex: 1; padding: 4px; font-size: 11px; border: 1px solid #ccc; border-radius: 3px;"; row.appendChild(label); row.appendChild(input); configPanel.appendChild(row); }); const btnSaveConfig = document.createElement('button'); btnSaveConfig.id = "btn_save_config"; btnSaveConfig.style.cssText = "width: 100%; padding: 8px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; margin-top: 8px;"; btnSaveConfig.textContent = "💾 保存配置"; configPanel.appendChild(btnSaveConfig); configSection.appendChild(configPanel); div.appendChild(configSection); document.body.appendChild(div); } // --- 核心工具函数 --- function simulateClick(element) { if (!element) return; try { element.click(); } catch (e) { const events = ['mousedown', 'mouseup', 'click']; events.forEach(name => { const evt = new MouseEvent(name, { bubbles: true, cancelable: true }); element.dispatchEvent(evt); }); } // 补丁:模拟 Enter 键 try { element.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true })); } catch (e) { } } function setNativeValue(element, value) { if (!element) return; const lastValue = element.value; element.value = value; const tracker = element._valueTracker; if (tracker) tracker.setValue(lastValue); element.dispatchEvent(new Event('input', { bubbles: true })); element.dispatchEvent(new Event('change', { bubbles: true })); element.dispatchEvent(new Event('blur', { bubbles: true })); } function pressEnter(element) { try { ['keydown', 'keypress', 'keyup'].forEach(type => { element.dispatchEvent(new KeyboardEvent(type, { bubbles: true, cancelable: true, key: 'Enter', code: 'Enter', keyCode: 13, which: 13, charCode: 13 })); }); } catch (e) { console.warn("[pressEnter] Failed:", e); } } // 专门针对下拉框的智能填值 (自动匹配 Option Value) function setDropdownValue(element, textOrValue) { if (!element) return; // 0. 预处理:模拟用户点击以激活下拉菜单(对 React 组件很重要) try { element.focus(); simulateClick(element); } catch (e) { } // 1. 如果是标准 SELECT,尝试按文本匹配 Option if (element.tagName === 'SELECT') { // Wait for options to load (Lazy loading check) if (element.options.length === 0) { console.warn("[Dropdown] Options empty, waiting/retrying..."); try { element.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); } catch (e) { } return; } const opts = Array.from(element.options); const target = (textOrValue || "").toString().toLowerCase().trim(); // A. Prefer Exact Match (Text or Value) let foundOption = opts.find(opt => opt.text.toLowerCase().trim() === target || opt.value.toLowerCase().trim() === target ); // B. Fuzzy Match Text (Contains) if (!foundOption) { foundOption = opts.find(opt => opt.text.toLowerCase().includes(target)); } // C. Month Name to Value Mapping fallback (e.g. "May" -> "05") if (!foundOption) { const months = ["january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december"]; const idx = months.indexOf(target); if (idx !== -1) { const val1 = (idx + 1).toString(); // "5" const val2 = (idx + 1).toString().padStart(2, '0'); // "05" foundOption = opts.find(opt => opt.value === val1 || opt.value === val2); } } if (foundOption) { console.log(`[Dropdown] Matched "${textOrValue}" to option value "${foundOption.value}" (Text: "${foundOption.text}")`); setNativeValue(element, foundOption.value); pressEnter(element); // Simulate Enter key return; } else { // Diagnostics: Log first 5 options to help debug const debugOpts = opts.slice(0, 5).map(o => `"${o.text}"=${o.value}`).join(', '); console.warn(`[Dropdown] No match for "${textOrValue}". Available: [${debugOpts}...]`); } } // 2. Fallback to raw input (for custom dropdowns) console.warn(`[Dropdown] Fallback to raw input for "${textOrValue}"`); setNativeValue(element, textOrValue); // 对自定义下拉组件,模拟 Enter 键来确认选择 pressEnter(element); } function getExactBranch(text) { const upper = (text || "").toUpperCase(); if (upper.includes("SPACE FORCE")) return "Space Force"; if (upper.includes("ARMY")) return "Army"; if (upper.includes("NAVY")) return "Navy"; if (upper.includes("MARINE")) return "Marine Corps"; if (upper.includes("AIR FORCE")) return "Air Force"; if (upper.includes("COAST GUARD")) return "Coast Guard"; return "Army"; } function scrapeGraveLocator() { const rows = document.querySelectorAll('#searchResults tbody tr'); let records = []; let currentRecord = {}; rows.forEach(row => { const itemNum = row.querySelector('.item-number'); if (itemNum) { if (currentRecord.lastName) records.push(currentRecord); currentRecord = { branch: "Army" }; } const header = row.querySelector('.row-header')?.innerText || ""; const value = row.querySelector('.results-info')?.innerText || ""; if (header.includes("Name:")) { const parts = value.split(','); currentRecord.lastName = parts[0]?.trim(); currentRecord.firstName = parts[1]?.trim(); } else if (header.includes("Rank & Branch:")) { currentRecord.branch = getExactBranch(value); } else if (header.includes("Date of Birth:")) { const parts = value.match(/(\d{2})\/(\d{2})\/(\d{4})/); if (parts) { currentRecord.bMonth = MONTH_MAP[parts[1]]; currentRecord.bDay = parts[2]; currentRecord.bYear = parts[3]; } } else if (header.includes("Date of Death:")) { const parts = value.match(/(\d{2})\/(\d{2})\/(\d{4})/); if (parts) { currentRecord.dMonth = MONTH_MAP[parts[1]]; currentRecord.dDay = parts[2]; } } }); if (currentRecord.lastName) records.push(currentRecord); const cfg = getConfig(); return records.filter(r => r.bYear && parseInt(r.bYear) >= cfg.MIN_BIRTH_YEAR).map(r => [ cfg.FIXED_STATUS, r.branch, r.firstName, r.lastName, r.bMonth, r.bDay, r.bYear, r.dMonth || "January", r.dDay || "01", cfg.FIXED_DISCHARGE_YEAR, cfg.FIXED_EMAIL ]); } // --- 自动化循环 --- async function runAutomation() { if (!getIsRunning()) return; const host = location.host; // 1. ChatGPT 自动点击 (不含跳转 Outlook) if (host.includes('chatgpt.com')) { // V17: ChatGPT 仅作为初始启动入口,不再负责后续循环 const btn = Array.from(document.querySelectorAll('a, button, [role="button"]')).find(el => { const text = (el.textContent || "").toLowerCase(); return CHATGPT_CLAIM_TEXTS.some(t => text.includes(t.toLowerCase())) || el.href?.includes('sheerid.com'); }); if (btn) { const targetUrl = btn.href; if (targetUrl && targetUrl.includes('sheerid.com')) { setStatus("🚀 强制接管链接并开启所属权..."); GM_openInTab(targetUrl, { active: true, insert: true, setParent: true }); // 我们不需要在这里点击,因为 openInTab 已经处理了跳转 // 也不需要关闭自身,ChatGPT 是总控 } else { setStatus("🚀 点击验证按钮..."); btn.click(); } } } // 2. SheerID 验证流程 (V17 极简架构) else if (host.includes('services.sheerid.com')) { const urlParams = new URLSearchParams(window.location.search); const hasEmailToken = urlParams.has('emailToken'); // === T2 (验证页): 幽灵模式 === // 只要一打开,就说明 Outlook 已经点击了链接。等待后端处理完,直接关闭。 if (hasEmailToken) { setStatus("🏁 验证页: 激活后端验证..."); // 给后端一点时间处理,然后自毁 setTimeout(() => { console.log("[T2] Backend should be done. Closing."); window.close(); }, 1500); return; } // === T1 (表单页): 持久循环模式 === // 逻辑: 填表 -> 提交 -> 等待/成功 -> 刷新 -> 只有看到 Try Again 才点击 -> 回到表单 const pageText = document.body.textContent.toLowerCase(); const firstNameEl = document.querySelector(FIELD_MAP.firstName); // A. 检测 "Try Again" / 重置按钮 (这是回到表单的唯一路径) // 注意: 一些页面可能用 'Retry' 或 'Verify another person' const retryBtn = Array.from(document.querySelectorAll('button, a')).find(el => { const t = (el.textContent || "").trim(); return SHEERID_RETRY_TEXTS.some(kw => t.toLowerCase() === kw.toLowerCase() || t.includes(kw)); }); if (retryBtn) { setStatus("🔄 发现重置按钮,点击以开始新任务..."); retryBtn.click(); return; } // B. 检测等待或完成信令 const WAIT_KEYWORDS = ["check your email", "sent an email", "verification email"]; const SUCCESS_KEYWORDS = ["you've been verified", "you have been verified", "success", "you're confirmed", "congratulations"]; // Continue 按钮是验证成功的另一个标志 const hasContinueBtn = !!Array.from(document.querySelectorAll('button, a')).find(el => (el.textContent || "").toLowerCase().trim() === 'continue' ); // 只有在没有表单的情况下才判定这些状态,防止误判 if (!firstNameEl) { const isWait = WAIT_KEYWORDS.some(k => pageText.includes(k)); const isSuccess = SUCCESS_KEYWORDS.some(k => pageText.includes(k)) || hasContinueBtn; // 错误页也视为等待重置的状态 (包含 "We could not verify" / "Unable to verify") const hasError = pageText.includes("error") || pageText.includes("limit exceeded") || pageText.includes("unable to verify") || pageText.includes("could not verify"); // 错误页处理:如果是 "Verification Limit Exceeded",通常没有重试按钮,直接跳转回 ChatGPT const isLimitError = pageText.includes("verification limit exceeded") || pageText.includes("already redeemed"); if (isWait || isSuccess || hasError) { const statusStr = isSuccess ? "✅ 验证成功" : (hasError ? "❌ 发生错误" : "⏳ 等待邮件链接点击..."); setStatus(`${statusStr} | 3秒后刷新检测状态...`); // 关键修复:任务已完成/挂起,立即清空当前任务,以便下一轮领取新任务 if (getCurrentTask()) { console.log("[V17] Terminal state reached. Clearing current task."); setCurrentTask(null); } if (isWait) setTaskStage('AWAITING_EMAIL'); if (isSuccess) setTaskStage('COMPLETED'); // 遇到致命错误(达到上限),直接重置回 ChatGPT if (isLimitError) { setStatus("❌ 达到验证上限,强制重置..."); setTimeout(() => location.href = "https://chatgpt.com/veterans-claim", 2000); return; } // V17 核心: 不断刷新,直到页面变样 // 使用 setInterval 而不是 setTimeout,防止浏览器后台休眠导致计时器暂停 // 并尝试夺取焦点 window.focus(); setTimeout(() => location.reload(), 3000); return; } } // C. 填表逻辑 (仅当看到表单时) if (firstNameEl) { const queue = getQueue(); let currentTask = getCurrentTask(); const currentStage = getTaskStage(); // 自我修复: 如果之前是等待/完成/填写/提交状态,但当前没任务,说明是上一轮的残留状态 if (!currentTask && (currentStage === 'AWAITING_EMAIL' || currentStage === 'COMPLETED' || currentStage === 'FILLING' || currentStage === 'SUBMITTING')) { console.log(`[V17] State Reset: ${currentStage} -> IDLE`); setTaskStage('IDLE'); } if (!currentTask && queue.length > 0 && getTaskStage() === 'IDLE') { currentTask = queue.shift(); saveQueue(queue); setCurrentTask(currentTask); setTaskStage('FILLING'); } if (currentTask) { setTaskStage('FILLING'); setStatus(`📝 正在填写: ${currentTask[2]} ${currentTask[3]}`); const statusEl = document.querySelector(FIELD_MAP.status); // A. 填写 Status (V14 逻辑: 只在值不对时才填) const cfg = getConfig(); if (statusEl) { if (statusEl.value !== cfg.FIXED_STATUS) { statusEl.focus(); simulateClick(statusEl); await new Promise(r => setTimeout(r, 100)); setNativeValue(statusEl, cfg.FIXED_STATUS); statusEl.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); await new Promise(r => setTimeout(r, 500)); } } // B. 填写详细信息 (V14 逻辑) const branchEl = document.querySelector(FIELD_MAP.branch); if (branchEl) { branchEl.focus(); simulateClick(branchEl); await new Promise(r => setTimeout(r, 50)); setNativeValue(branchEl, currentTask[1]); branchEl.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); await new Promise(r => setTimeout(r, 100)); } setNativeValue(document.querySelector(FIELD_MAP.firstName), currentTask[2]); setNativeValue(document.querySelector(FIELD_MAP.lastName), currentTask[3]); const bmEl = document.querySelector(FIELD_MAP.bMonth); if (bmEl) { bmEl.focus(); simulateClick(bmEl); await new Promise(r => setTimeout(r, 50)); setNativeValue(bmEl, currentTask[4]); bmEl.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); } setNativeValue(document.querySelector(FIELD_MAP.bDay), currentTask[5]); setNativeValue(document.querySelector(FIELD_MAP.bYear), currentTask[6]); const dmEl = document.querySelector(FIELD_MAP.dMonth); if (dmEl) { dmEl.focus(); simulateClick(dmEl); await new Promise(r => setTimeout(r, 50)); setNativeValue(dmEl, currentTask[7]); dmEl.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); } setNativeValue(document.querySelector(FIELD_MAP.dDay), currentTask[8]); setNativeValue(document.querySelector(FIELD_MAP.dYear), currentTask[9]); setNativeValue(document.querySelector(FIELD_MAP.email), currentTask[10]); // C. 点击提交按钮 (V14 逻辑: 直接检查并点击) const submitBtn = document.querySelector(SUBMIT_BTN_SELECTOR); if (submitBtn && submitBtn.getAttribute('aria-disabled') !== 'true') { setTaskStage('SUBMITTING'); submitBtn.click(); } return; } } // D. 空闲等待 (无表单,无结果,无任务) if (!firstNameEl && !retryBtn) { // 可能是加载中,或者是未知的中间状态,稍微刷新一下保活 // setTimeout(() => location.reload(), 5000); } } // 3. SheerID 联动扫描 (仅在等待邮件阶段生效) else if (host.includes('outlook.')) { processOutlookEmails(); } // 4. 重置状态 (如果 host 不匹配且正在运行) else if (getIsRunning()) { setStatus("📡 脚本运行中 | 监听特定页面..."); } } // --- UI/事件控制 --- function setStatus(msg) { const area = document.getElementById('status_area'); if (area) area.innerText = msg; } function updateUI() { const queue = getQueue(); const running = getIsRunning(); const btn = document.getElementById('btn_toggle'); const count = document.getElementById('queue_count'); const statusArea = document.getElementById('status_area'); if (count) count.innerText = queue.length; if (btn) { btn.innerText = running ? "⏸️ 运行中" : "▶️ 启动助手"; btn.style.background = running ? "#198754" : "#0d6efd"; } // 增强状态显示 if (statusArea) { if (!running) { statusArea.innerText = "⏸️ 助手已暂停"; statusArea.style.color = "#6c757d"; } else if (queue.length === 0 && !getCurrentTask()) { statusArea.innerText = "📭 队列为空,等待输入..."; statusArea.style.color = "#dc3545"; } else if (statusArea.innerText === "待命中...") { statusArea.innerText = "📡 正在寻找目标表单..."; statusArea.style.color = "#0d6efd"; } } } function bindEvents() { document.getElementById('btn_toggle').onclick = () => setIsRunning(!getIsRunning()); document.getElementById('btn_skip').onclick = () => { setCurrentTask(null); setStatus("⏭️ 已跳过..."); }; document.getElementById('btn_scrape').onclick = () => { const data = scrapeGraveLocator(); saveQueue(getQueue().concat(data)); alert(`捕捉到 ${data.length} 条数据`); }; document.getElementById('btn_import').onclick = () => { try { const data = JSON.parse(document.getElementById('bulk_input').value); saveQueue(getQueue().concat(data)); alert("导入成功"); } catch (e) { alert("JSON 格式错误"); } }; document.getElementById('btn_reset').onclick = () => { if (confirm("清空并重置?")) { GM_deleteValue('global_auth_queue'); GM_deleteValue('current_active_task'); GM_deleteValue('is_script_running'); location.reload(); } }; // 配置面板事件 document.getElementById('btn_config_toggle').onclick = () => { const panel = document.getElementById('config_panel'); const btn = document.getElementById('btn_config_toggle'); if (panel.style.display === 'none') { panel.style.display = 'block'; btn.textContent = '⚙️ 隐藏配置'; } else { panel.style.display = 'none'; btn.textContent = '⚙️ 显示配置'; } }; document.getElementById('btn_save_config').onclick = () => { const newConfig = { FIXED_EMAIL: document.getElementById('cfg_email').value, FIXED_STATUS: document.getElementById('cfg_status').value, FIXED_DISCHARGE_YEAR: document.getElementById('cfg_discharge_year').value, MIN_BIRTH_YEAR: parseInt(document.getElementById('cfg_min_birth_year').value) || 1930, SENDER_FILTER: document.getElementById('cfg_sender_filter').value }; saveConfig(newConfig); alert('✅ 配置已保存!'); }; } // --- 初始化 --- function init() { const host = location.host; if (host.includes('outlook.')) { // Outlook 需要等待 body 稳定 const checkBody = setInterval(() => { if (document.body) { clearInterval(checkBody); createOutlookPanel(); setInterval(() => { if (getIsRunning()) processOutlookEmails(); }, getOutlookConfig().checkInterval); } }, 500); } else { createPanel(); bindEvents(); updateUI(); setInterval(runAutomation, 3000); } } init(); })();