// ==UserScript== // @name 神秘采集助手 // @namespace http://tampermonkey.net/ // @version 18.1 // @description 主动寻址跳转解绑页、防残影排重、完美解决 Excel 科学计数法吞数字、防沙箱拦截 // @author You // @match *://*.scac.edu.cn/* // @match *://*.chaoxing.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @run-at document-end // ==/UserScript== (function() { 'use strict'; const MSG_TYPE = 'BHP_DATA_FOUND'; // ========================================== // 1. 内部沙箱间谍 (负责在 iframe 内部“偷听”数据) // ========================================== if (window.top !== window.self) { const spyScanner = setInterval(() => { let foundPhone = null; try { // 优先读取内存变量(新页面的 var phone1='xxx') if (window.phone1) foundPhone = window.phone1; else if (window.studentData && window.studentData.phone) foundPhone = window.studentData.phone; // 读 JSON 源码 if (!foundPhone && document.documentElement) { const html = document.documentElement.innerHTML; const jsonMatch = html.match(/"phone"\s*:\s*"?(1[3-9]\d{9})"?/i); if (jsonMatch) foundPhone = jsonMatch[1]; } // 暴力检索页面文本 if (!foundPhone && document.body) { const text = document.body.innerText; const textMatch = text.match(/(?:^|[^\d])(1[3-9]\d{9})(?:[^\d]|$)/); if (textMatch) foundPhone = textMatch[1]; } if (foundPhone) window.top.postMessage({ type: MSG_TYPE, phone: foundPhone }, '*'); } catch (e) {} }, 1000); return; } // ========================================== // 2. 主界面控制台 // ========================================== GM_addStyle(` #batch-helper-panel { position: fixed; right: 20px; top: 20px; width: 360px; background: #fff; box-shadow: 0 10px 25px rgba(0,0,0,0.2); border-radius: 8px; z-index: 2147483647; font-family: system-ui, sans-serif; overflow: hidden; border: 1px solid #e5e7eb; display: flex; flex-direction: column; max-height: 90vh; } .bhp-header { background: #4D58B5; color: white; padding: 12px 16px; font-weight: bold; display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; } .bhp-body { padding: 16px; display: flex; flex-direction: column; gap: 10px; overflow-y: auto; } .bhp-textarea { width: 100%; height: 80px; border: 1px solid #d1d5db; border-radius: 4px; padding: 8px; font-size: 12px; resize: vertical; box-sizing: border-box; flex-shrink: 0; } .bhp-btn { padding: 8px 10px; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; font-weight: 500; transition: background 0.2s; color: white; text-align: center; } .bhp-btn-primary { background: #3b82f6; } .bhp-btn-primary:hover { background: #2563eb; } .bhp-btn-danger { background: #ef4444; } .bhp-btn-danger:hover { background: #dc2626; } .bhp-btn-warning { background: #f59e0b; } .bhp-btn-warning:hover { background: #d97706; } .bhp-btn-success { background: #10b981; } .bhp-btn-success:hover { background: #059669; } .bhp-btn-gray { background: #6b7280; } .bhp-btn-gray:hover { background: #4b5563; } .bhp-btn:disabled { opacity: 0.5; cursor: not-allowed; } .bhp-status { font-size: 12px; color: #4b5563; background: #f3f4f6; padding: 10px; border-radius: 4px; word-break: break-all; line-height: 1.4; flex-shrink: 0;} .bhp-row { display: flex; gap: 8px; flex-shrink: 0; } .bhp-row > * { flex: 1; } .bhp-history-box { border: 1px solid #e5e7eb; border-radius: 4px; background: #fafafa; display: flex; flex-direction: column; min-height: 150px; max-height: 250px; } .bhp-history-title { font-size: 11px; font-weight: bold; padding: 6px 8px; border-bottom: 1px solid #e5e7eb; background: #f3f4f6; color: #4b5563; display: flex; justify-content: space-between; } .bhp-history-list { overflow-y: auto; padding: 4px 8px; flex-grow: 1; } .bhp-history-item { font-size: 11px; padding: 6px 0; border-bottom: 1px dashed #e5e7eb; display: flex; justify-content: space-between; align-items: center; gap: 4px; } .bhp-history-item:last-child { border-bottom: none; } .bhp-mini-btn { padding: 2px 5px; border: 1px solid #d1d5db; border-radius: 3px; background: #fff; cursor: pointer; font-size: 10px; color: #374151; transition: all 0.2s;} .bhp-mini-btn:hover { background: #f3f4f6; border-color: #9ca3af; } `); class BatchController { constructor() { this.state = GM_getValue('batch_state', { isRunning: false, queue: [], results: [], currentIndex: 0 }); this.isProcessing = false; this.initUI(); this.listenToSpy(); this.routeLogic(); } saveState() { GM_setValue('batch_state', this.state); this.updateUI(); } // 全局排重拦截 isDuplicatePhone(phone) { return this.state.results.some(r => r.phone === phone); } listenToSpy() { window.addEventListener('message', (event) => { if (!this.state.isRunning || this.isProcessing) return; if (event.data && event.data.type === MSG_TYPE) { const phone = event.data.phone; const current = this.state.queue[this.state.currentIndex]; if (current && !current.id.includes(phone)) { if (!this.isDuplicatePhone(phone)) { this.isProcessing = true; this.recordAndNext(phone); } } } }); } routeLogic() { if (!this.state.isRunning) return; const path = window.location.pathname; if (path.includes('/portal/login') || path === '/' || path === '') { this.handleLogin(); } else { this.waitForData(); } } handleLogin() { if (this.state.currentIndex >= this.state.queue.length) { this.updateStatus('✅ 自动队列执行完毕!请添加新账号或导出 CSV。'); this.state.isRunning = false; this.saveState(); return; } const task = this.state.queue[this.state.currentIndex]; this.updateStatus(`▶️ 正在提取 (${this.state.currentIndex + 1}/${this.state.queue.length})
账号: ${task.id}
⏳ 请输入验证码并回车`); const checkNode = setInterval(() => { const u = document.querySelector('#userName'); const p = document.querySelector('#passWord'); const c = document.querySelector('#verifyCode'); const e = document.querySelector('#loginMsg') || document.querySelector('#accountloginMsg'); if (u && p && c) { clearInterval(checkNode); if (u.value !== task.id) { u.value = task.id; p.value = task.pwd; } c.focus(); const obs = new MutationObserver(() => { const txt = e.innerText.trim(); if (txt) { this.updateStatus(`❌ 登录异常: ${txt}
请手动修正或点击跳过`); document.getElementById('bhp-btn-skip').disabled = false; } }); obs.observe(e, { childList: true, characterData: true, subtree: true }); } }, 500); } waitForData() { this.updateStatus('✅ 登录成功!正在寻址【解绑微信】数据页...'); let navAttempts = 0; let hasNavigated = false; let timerCount = 0; const timer = setInterval(() => { if (this.isProcessing) { clearInterval(timer); return; } // --- 1. 第一步:主动跳转到解绑页 --- if (!hasNavigated) { navAttempts++; // 找到左侧带有数据 URL 的菜单 const targetNode = document.querySelector('[dataurl*="myMessage/listUI"]') || document.querySelector('div[name*="解绑微信"]'); const iframe = document.getElementById('frame_content'); if (targetNode && iframe) { const url = targetNode.getAttribute('dataurl'); iframe.src = url; // 强行把内联框架重定向过去 hasNavigated = true; this.updateStatus('🔓 成功拿到入口 Token,正在加载个人信息...'); } else if (navAttempts >= 20) { // 等10秒菜单还没出来直接跳过 this.isProcessing = true; clearInterval(timer); this.recordAndNext('失败(无左侧菜单)'); } return; } // --- 2. 第二步:等待沙箱间谍传回数据 --- timerCount++; // 兜底:外层扫描 iframe 内部(防止跨域失效的双重保险) try { const iframe = document.getElementById('frame_content'); if (iframe && iframe.contentDocument) { const fullText = iframe.contentDocument.body.innerText; const matches = fullText.match(/(?:^|[^\d])(1[3-9]\d{9})(?:[^\d]|$)/g); if (matches) { const id = this.state.queue[this.state.currentIndex].id; for (let m of matches) { let phone = m.replace(/[^\d]/g, ''); if (!id.includes(phone)) { if (this.isDuplicatePhone(phone)) { this.updateStatus(`🛡️ 拦截到缓存残影: ${phone}
正在等待新页面渲染 (${Math.floor(timerCount/2)}s)...`); } else { this.isProcessing = true; clearInterval(timer); this.recordAndNext(phone); return; } } } } else { this.updateStatus(`📡 正在扫描个人信息页 (已耗时 ${Math.floor(timerCount/2)}s)...`); } } } catch(e) {} if (timerCount >= 40) { // 20秒超时 clearInterval(timer); if (!this.isProcessing) { this.isProcessing = true; this.recordAndNext('失败(加载超时)'); } } }, 500); } async recordAndNext(phone) { const task = this.state.queue[this.state.currentIndex]; this.state.results.push({ id: task.id, pwd: task.pwd, phone: phone, status: phone.includes('失败') ? '失败' : '抓取成功' }); this.state.currentIndex++; this.saveState(); this.updateStatus(`✅ 捕获成功: ${phone}
准备注销切换...`); setTimeout(async () => { try { await fetch('/base/logout'); } catch(e) {} window.location.href = 'https://celppx.scac.edu.cn/portal/login'; }, 1500); } skipCurrent() { const task = this.state.queue[this.state.currentIndex]; this.state.results.push({ id: task.id, pwd: task.pwd, phone: '-', status: '跳过' }); this.state.currentIndex++; this.saveState(); window.location.href = 'https://celppx.scac.edu.cn/portal/login'; } // ================= 解决沙箱隔离的按钮事件 ================= editItem(idx) { const item = this.state.results[idx]; const newPhone = prompt(`修改账号 ${item.id} 的手机号:`, item.phone); if (newPhone !== null) { item.phone = newPhone.trim(); item.status = '手动修改'; this.saveState(); } } deleteItem(idx) { if (confirm('确定从列表中删除此记录吗?')) { this.state.results.splice(idx, 1); this.saveState(); } } retryItem(idx) { if (!confirm('确定要重新抓取此账号吗?')) return; const item = this.state.results.splice(idx, 1)[0]; this.state.queue.push({ id: item.id, pwd: item.pwd }); this.state.isRunning = true; this.saveState(); window.location.href = 'https://celppx.scac.edu.cn/portal/login'; } startBatch(val) { const lines = val.split('\n'); let hasNewTask = false; lines.forEach(l => { const p = l.split(/[, \t]+/); if (p.length >= 2 && p[0].trim() !== '') { let id = p[0].trim(); let p1 = p[1].trim(); let p2 = p.length > 2 ? p[2].trim() : ''; if (p2 !== '') { let phone = '', pwd = ''; if (/^1[3-9]\d{9}$/.test(p1)) { phone = p1; pwd = p2; } else { pwd = p1; phone = p2; } this.state.results.push({ id, pwd, phone, status: '三段直录' }); } else { if (/^1[3-9]\d{9}$/.test(id)) { this.state.results.push({ id: id, pwd: p1, phone: id, status: '账号即手机(免查)' }); } else { this.state.queue.push({ id, pwd: p1 }); hasNewTask = true; } } } }); if (hasNewTask) { this.state.isRunning = true; this.saveState(); window.location.href = 'https://celppx.scac.edu.cn/portal/login'; } else { this.saveState(); this.updateStatus('✅ 提交的数据已通过智能规则免登录录入,无需自动化排队。'); } } clearCache() { if (confirm("确定要清空所有历史查询记录吗?清空后将无法恢复。")) { this.state = { isRunning: false, queue: [], results: [], currentIndex: 0 }; this.saveState(); this.updateStatus("🗑️ 历史记录已清空,可开始新任务。"); } } exportCSV() { if (!this.state.results.length) return alert('当前没有可导出的数据!'); let csv = "\uFEFF身份证号,手机号,密码,状态\n"; this.state.results.forEach(r => { csv += `"${r.id}\t","${r.phone}\t","${r.pwd}","${r.status}"\n`; }); const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `安管提取结果_${new Date().getTime()}.csv`; a.click(); } initUI() { const p = document.createElement('div'); p.id = 'batch-helper-panel'; p.innerHTML = `
数据工作台 V18.0 [隐藏]
就绪,可分批次粘贴运行,结果会自动累加。
已录入/查询 (共 0 条)
`; document.body.appendChild(p); document.getElementById('bhp-btn-start').onclick = () => this.startBatch(document.getElementById('bhp-input').value); document.getElementById('bhp-btn-stop').onclick = () => { this.state.isRunning = false; this.saveState(); this.updateStatus('已手动终止。'); }; document.getElementById('bhp-btn-skip').onclick = () => this.skipCurrent(); document.getElementById('bhp-btn-export').onclick = () => this.exportCSV(); document.getElementById('bhp-btn-clear').onclick = () => this.clearCache(); document.getElementById('bhp-history-list').addEventListener('click', (e) => { const idx = e.target.getAttribute('data-idx'); if (idx === null) return; const index = parseInt(idx, 10); if (e.target.classList.contains('bhp-action-edit')) this.editItem(index); if (e.target.classList.contains('bhp-action-retry')) this.retryItem(index); if (e.target.classList.contains('bhp-action-del')) this.deleteItem(index); }); this.updateUI(); } updateUI() { document.getElementById('bhp-input').style.display = this.state.isRunning ? 'none' : 'block'; document.getElementById('bhp-btn-start').disabled = this.state.isRunning; document.getElementById('bhp-btn-clear').disabled = this.state.isRunning; const histList = document.getElementById('bhp-history-list'); document.getElementById('bhp-hist-count').innerText = this.state.results.length; if (this.state.results.length === 0) { histList.innerHTML = '
暂无历史记录
'; } else { let html = ''; this.state.results.forEach((r, idx) => { let color = (r.status.includes('成功') || r.status.includes('录入') || r.status.includes('免查') || r.status.includes('修改')) ? '#10b981' : (r.status.includes('失败') ? '#ef4444' : '#f59e0b'); let shortId = r.id.substring(0, 4) + '****' + r.id.substring(r.id.length - 4); html += `
${idx + 1}. ${shortId} ${r.phone === '-' ? '暂无' : r.phone}
`; }); histList.innerHTML = html; histList.scrollTop = histList.scrollHeight; } } updateStatus(h) { document.getElementById('bhp-status').innerHTML = h; } } new BatchController(); })();