// ==UserScript==
// @name 四川安管平台 - 批量提取助手 (V18新版适应)
// @namespace http://tampermonkey.net/
// @version 18.0
// @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 = `