// ==UserScript== // @name 超星学习通人脸强制通过(QQ交流群:164902340) // @namespace http://tampermonkey.net/ // @version 1.0.1 // @description 实现稳定的超星MOOC人脸识别,自动通过 // @author Doubao // @match https://mooc1-2.chaoxing.com/mooc-ans/mycourse/studentstudy* // @match https://mooc1-2.chaoxing.com/visit/stucoursemiddle* // @match https://mooc1.chaoxing.com/mooc-ans/mycourse/studentstudy* // @match https://mooc1.chaoxing.com/visit/stucoursemiddle* // @match https://mooc2-1.chaoxing.com/mooc-ans/mycourse/studentstudy* // @match https://mooc2-1.chaoxing.com/visit/stucoursemiddle* // @match https://mooc1.chaoxing.com/mycourse/studentstudy* // @grant GM_download // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @require https://cdn.jsdelivr.net/npm/crypto-js@4.1.1/crypto-js.min.js // ==/UserScript== (function() { 'use strict'; // 全局状态管理 const globalState = { isProcessing: false, lastQRUrl: '', lastProcessTime: 0, initialized: false }; // 日志系统 const logger = { info: (msg) => console.log(`[超星人脸助手] ${msg}`), error: (msg) => console.error(`[超星人脸助手] ${msg}`), debug: (msg) => console.debug(`[超星人脸助手] ${msg}`), warn: (msg) => console.warn(`[超星人脸助手] ${msg}`), verbose: (msg) => console.log(`[超星人脸助手-详细] ${msg}`) }; // 防抖函数 function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // Session管理 const session = { cookies: {}, setCookie: (key, value) => { session.cookies[key] = value; }, getCookie: (key) => { return session.cookies[key] || null; }, get: (url, params, callback) => { const queryString = params ? '?' + Object.keys(params).map(key => encodeURIComponent(key) + '=' + encodeURIComponent(params[key]) ).join('&') : ''; fetch(url + queryString, { method: 'GET', credentials: 'include' }).then(response => { if (response.ok) { return response.arrayBuffer ? response.arrayBuffer() : response.text(); } else { throw new Error(`HTTP ${response.status}`); } }).then(data => { callback({ error: false, status: 200, responseText: typeof data === 'string' ? data : null, response: typeof data !== 'string' ? data : null }); }).catch(error => { callback({ error: true, status: 500, message: error.message }); }); }, post: (url, data, callback, headers = {}) => { fetch(url, { method: 'POST', body: data, credentials: 'include', headers: headers }).then(response => { return response.text().then(text => ({ status: response.status, responseText: text, error: !response.ok })); }).then(result => { callback(result); }).catch(error => { callback({ error: true, status: 500, message: error.message }); }); } }; // 通知系统 function showCustomNotification(title, message, type = 'info') { const colors = { success: '#4CAF50', error: '#f44336', warning: '#ff9800', info: '#2196F3' }; const notification = document.createElement('div'); notification.style.cssText = ` position: fixed; top: 20px; right: 20px; background: ${colors[type]}; color: white; padding: 15px; border-radius: 5px; z-index: 10001; max-width: 300px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); `; notification.innerHTML = `${title}
${message}`; document.body.appendChild(notification); setTimeout(() => { if (notification.parentNode) { notification.parentNode.removeChild(notification); } }, 5000); } // QR码参数提取器 class FaceQRExtractor { constructor() { this.qrParams = {}; this.hiddenFields = {}; } // 提取QR码URL extractQRUrl() { const fcqrimg = document.getElementById('fcqrimg'); if (fcqrimg && fcqrimg.src) { return fcqrimg.src; } return null; } // 解析URL参数 parseUrlParams(url) { const params = {}; try { const urlObj = new URL(url, window.location.origin); urlObj.searchParams.forEach((value, key) => { params[key] = value; }); if (params.content) { try { const decodedContent = decodeURIComponent(params.content); const contentUrl = new URL(decodedContent); contentUrl.searchParams.forEach((value, key) => { params[key] = value; }); } catch (e) { logger.warn('解析content参数失败: ' + e.message); } } } catch (e) { logger.error('解析URL失败: ' + e.message); } return params; } // 提取隐藏字段 extractHiddenFields() { const hiddenInputs = document.querySelectorAll('input[type="hidden"]'); const fields = {}; hiddenInputs.forEach(input => { if (input.id && input.value) { fields[input.id] = input.value; } }); return fields; } // 获取所有参数 getAllParams() { const qrUrl = this.extractQRUrl(); if (!qrUrl) { return null; } // 检查是否与上次相同,避免重复处理 if (qrUrl === globalState.lastQRUrl) { return null; } globalState.lastQRUrl = qrUrl; this.qrParams = this.parseUrlParams(qrUrl); this.hiddenFields = this.extractHiddenFields(); // 从当前页面URL获取参数 const currentUrlParams = new URLSearchParams(window.location.search); const allParams = { ...this.qrParams, ...this.hiddenFields, // 优先使用QR码和隐藏字段的参数,如果没有则从当前URL获取 courseid: this.hiddenFields.curCourseId || this.qrParams.courseid || currentUrlParams.get('courseid'), clazzid: this.hiddenFields.curClazzId || this.qrParams.clazzid || currentUrlParams.get('clazzid'), cpi: this.hiddenFields.curCpi || this.qrParams.cpi || currentUrlParams.get('cpi'), knowledgeid: this.hiddenFields.curChapterId || this.qrParams.knowledgeid || currentUrlParams.get('knowledgeid') }; return allParams; } } // 初始化Cookie function initCookies() { const cookies = document.cookie.split(';'); cookies.forEach(cookie => { const [key, value] = cookie.trim().split('='); if (key && value) { session.setCookie(key, value); } }); } // 添加签名 function addInfEncSign(params) { if (!params) return {}; const query = Object.keys(params) .filter(key => params[key] !== undefined && params[key] !== null) .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`) .join("&") + "&DESKey=Z(AfY@XS"; return { ...params, "inf_enc": CryptoJS.MD5(query).toString() }; } // 获取时间戳函数 function getTs() { return Date.now(); } // 处理图片(整合两款脚本的图片处理逻辑,增强扰动) function processImageDataPromise(imgBytes) { return new Promise((resolve, reject) => { if (!imgBytes || imgBytes.byteLength === 0) { logger.error("图片数据为空,无法处理"); reject(new Error("图片数据为空")); return; } try { const uint8Array = new Uint8Array(imgBytes); const img = new Image(); const blob = new Blob([uint8Array], { type: 'image/jpeg' }); const imgUrl = URL.createObjectURL(blob); img.onload = () => { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); if (!ctx) throw new Error("无法获取Canvas上下文"); canvas.width = img.width; canvas.height = img.height; ctx.drawImage(img, 0, 0); const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); const pixels = imgData.data; // 增强扰动策略:整合两款脚本的扰动逻辑并增强 for (let i = 0; i < Math.min(15, pixels.length / 100); i++) { const y = Math.floor(Math.random() * img.height); const x = Math.floor(Math.random() * img.width) * 4; const c = Math.floor(Math.random() * 3); const val = Math.floor(Math.random() * 5) - 2; pixels[x + c] = Math.max(0, Math.min(255, pixels[x + c] + val)); } ctx.putImageData(imgData, 0, 0); canvas.toBlob((blob) => { if (!blob) throw new Error("无法生成图片Blob"); const reader = new FileReader(); reader.onload = (e) => { if (e.target.result) { const processedBytes = new Uint8Array(e.target.result); resolve(processedBytes); } else { throw new Error("无法读取图片数据"); } }; reader.onerror = () => { logger.error("读取处理后的图片数据失败"); reject(new Error("读取处理后的图片数据失败")); }; reader.readAsArrayBuffer(blob); }, 'image/jpeg', 0.8); } catch (e) { logger.error(`图片处理失败: ${e.message}`); reject(e); } finally { URL.revokeObjectURL(imgUrl); } }; img.onerror = () => { logger.error("图片加载失败"); reject(new Error("图片加载失败")); }; img.src = imgUrl; } catch (e) { logger.error(`处理图片数据失败: ${e.message}`); reject(e); } }); } // 提交人脸识别函数 function submitFaceRecognition(params, objectId) { return submitSign(params, objectId); } // 替换getUserTokenPromise函数(约第310行) function getUserTokenPromise() { return new Promise((resolve, reject) => { const url = 'https://pan-yz.chaoxing.com/api/token/uservalid'; // 检查Cookie是否存在 const puid = session.getCookie('_uid'); if (!puid) { logger.warn("未获取到用户ID,重新初始化Cookie"); initCookies(); } // 使用GM_xmlhttpRequest来避免CORS问题 GM_xmlhttpRequest({ method: 'GET', url: url, headers: { 'User-Agent': "Mozilla/5.0 (Linux; Android 12; SM-N9006 Build/V417IR; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/101.0.4951.61 Safari/537.36 (schild:a6aba57d51661f412e8b8d2177adf8b4) (device:SM-N9006) Language/zh_CN com.chaoxing.mobile/ChaoXingStudy_3_6.4.5_android_phone_10831_263 (@Kalimdor)_6e98c17c08204755905e1537b1933e72", 'Accept': 'application/json, text/plain, */*', 'Referer': window.location.href }, onload: function(response) { try { if (response.status !== 200) { throw new Error(`请求失败: ${response.status}`); } const data = JSON.parse(response.responseText); if (data && data._token) { resolve(data._token); } else { logger.error("获取token失败: " + (data?.message || "响应中缺少_token")); reject(new Error("获取token失败")); } } catch (e) { logger.error(`解析token响应失败: ${e.message}`); reject(e); } }, onerror: function(error) { logger.error(`Token请求失败: ${error}`); reject(new Error("Token请求失败")); } }); }); } // 替换getUserFaceImageDataPromise函数(约第350行) function getUserFaceImageDataPromise(puid) { return new Promise((resolve, reject) => { const url = "https://passport2-api.chaoxing.com/api/getUserFaceid"; const params = { "enc": CryptoJS.MD5(puid + "uWwjeEKsri").toString(), "token": "4faa8662c59590c6f43ae9fe5b002b42", "_time": getTs() }; const signedParams = addInfEncSign(params); const queryString = Object.keys(signedParams) .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(signedParams[key])}`) .join('&'); GM_xmlhttpRequest({ method: 'GET', url: `${url}?${queryString}`, headers: { 'User-Agent': "Mozilla/5.0 (Linux; Android 12; SM-N9006 Build/V417IR; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/101.0.4951.61 Safari/537.36 (schild:a6aba57d51661f412e8b8d2177adf8b4) (device:SM-N9006) Language/zh_CN com.chaoxing.mobile/ChaoXingStudy_3_6.4.5_android_phone_10831_263 (@Kalimdor)_6e98c17c08204755905e1537b1933e72", 'Accept': 'application/json, text/plain, */*', 'Referer': window.location.href }, onload: function(response) { try { if (response.status !== 200) { throw new Error(`请求失败: ${response.status}`); } const data = JSON.parse(response.responseText); if (data && data.result === 1 && data.data && data.data.http) { // 获取图片数据 GM_xmlhttpRequest({ method: 'GET', url: data.data.http, responseType: 'arraybuffer', onload: function(imgResponse) { if (imgResponse.status === 200 && imgResponse.response) { logger.info("成功获取人脸图片二进制数据"); resolve(imgResponse.response); } else { logger.error(`获取人脸图片数据失败,状态码: ${imgResponse.status}`); reject(new Error("获取人脸图片数据失败")); } }, onerror: function(error) { logger.error(`获取人脸图片数据失败: ${error}`); reject(new Error("获取人脸图片数据失败")); } }); } else { logger.error(`获取人脸图片URL失败: ${data?.msg || "未知错误"}`); reject(new Error("获取人脸图片URL失败")); } } catch (e) { logger.error(`解析人脸图片响应失败: ${e.message}`); reject(e); } }, onerror: function(error) { logger.error(`人脸图片URL请求失败: ${error}`); reject(new Error("人脸图片URL请求失败")); } }); }); } // 替换uploadFaceImagePromise函数(约第450行) function uploadFaceImagePromise(imgBytes, puid, uploadToken) { return new Promise((resolve, reject) => { const url = `https://pan-yz.chaoxing.com/upload?uploadtype=face&_token=${uploadToken}`; const formData = new FormData(); try { formData.append('file', new Blob([imgBytes], { type: 'image/jpeg' }), `${puid}.jpg`); formData.append('puid', puid); } catch (e) { logger.error(`创建FormData失败: ${e.message}`); reject(e); return; } GM_xmlhttpRequest({ method: 'POST', url: url, data: formData, headers: { 'User-Agent': "Mozilla/5.0 (Linux; Android 12; SM-N9006 Build/V417IR; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/101.0.4951.61 Safari/537.36 (schild:a6aba57d51661f412e8b8d2177adf8b4) (device:SM-N9006) Language/zh_CN com.chaoxing.mobile/ChaoXingStudy_3_6.4.5_android_phone_10831_263 (@Kalimdor)_6e98c17c08204755905e1537b1933e72", 'Accept': 'application/json, text/plain, */*', 'Referer': window.location.href }, onload: function(response) { if (response.status === 200) { try { const data = JSON.parse(response.responseText); logger.verbose && logger.verbose("图片上传完整响应: " + response.responseText); if (data && data.objectId) { logger.info("图片上传成功,获取到objectId"); resolve(data.objectId); } else { logger.error(`上传失败: ${data?.message || "响应中没有objectId"}`); reject(new Error(`上传失败: ${data?.message || "响应中没有objectId"}`)); } } catch (e) { logger.error(`解析上传响应失败: ${e.message}`); reject(e); } } else { logger.error(`图片上传失败,状态码: ${response.status}`); reject(new Error(`图片上传失败,状态码: ${response.status}`)); } }, onerror: function(error) { logger.error(`图片上传请求失败: ${error}`); reject(new Error("图片上传请求失败")); } }); }); } // 替换submitSign函数(约第550行) function submitSign(params, objectId, retryCount = 0) { const maxRetries = 10; const retryDelay = 5000; const url = `https://mooc1-api.chaoxing.com/mooc-ans/qr/updateqrstatus?uuid2=${params.uuid}&clazzId2=${params.clazzid}`; const formData = new FormData(); formData.append('clazzId', params.clazzid); formData.append('courseId', params.courseid); formData.append('uuid', params.uuid); formData.append('qrcEnc', params.qrcEnc); formData.append('cpi', params.cpi); formData.append('liveDetectionStatus', "1"); formData.append('objectId', objectId); formData.append('knowledgeid', params.knowledgeid || "0"); // 添加可能的空值参数 formData.append('signt', params.signt || ""); formData.append('signk', params.signk || ""); formData.append('cxtime', params.cxtime || ""); formData.append('cxcid', params.cxcid || ""); formData.append('videojobid', params.videojobid || ""); formData.append('videoCollectTime', params.realCollectTime || "0"); formData.append('chaptervideoobjectid', params.chaptervideoobjectid || ""); logger.info(`准备提交人脸请求 (重试次数: ${retryCount})`); // 打印FormData的详细内容 const formDataEntries = []; for (let [key, value] of formData.entries()) { formDataEntries.push(`${key}: ${value}`); } // 也可以打印为JSON格式 const formDataObj = {}; for (let [key, value] of formData.entries()) { formDataObj[key] = value; } logger.info(`准备提交人脸参数JSON: ${JSON.stringify(formDataObj, null, 2)}`); GM_xmlhttpRequest({ method: 'POST', url: url, data: formData, headers: { 'User-Agent': "Mozilla/5.0 (Linux; Android 12; SM-N9006 Build/V417IR; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/101.0.4951.61 Safari/537.36 (schild:a6aba57d51661f412e8b8d2177adf8b4) (device:SM-N9006) Language/zh_CN com.chaoxing.mobile/ChaoXingStudy_3_6.4.5_android_phone_10831_263 (@Kalimdor)_6e98c17c08204755905e1537b1933e72", 'Accept': "application/json, text/javascript, */*; q=0.01", 'X-Requested-With': "XMLHttpRequest", 'Referer': window.location.href }, onload: function(response) { try { const data = JSON.parse(response.responseText); logger.verbose && logger.verbose(`人脸完整响应: ${response.responseText}`); if (data && data.code === 0 && data.status === true && data.msg === "通过") { logger.info("人脸成功!"); showCustomNotification("人脸签到", "人脸验证成功完成", "success"); globalState.isProcessing = false; } else if (data && data.code === -4) { logger.error(`权限错误: ${data.msg || "你没有权限"}`); showCustomNotification("人脸识别失败", "权限不足,请重新登录", "error"); globalState.isProcessing = false; } else { logger.error(`人脸失败: ${data?.message || data?.msg || "未知错误"} (重试次数: ${retryCount})`); // 特殊处理"用户图片信息出错" if (data?.msg === "用户图片信息出错") { logger.info('检测到图片信息错误,将重新获取和处理图片'); showCustomNotification("图片错误", "图片信息有误,正在重新处理...", "warning"); } else { showCustomNotification("人脸识别失败", data?.message || data?.msg || "未知错误", "error"); } handleRetry(params, retryCount); } } catch (e) { if (response.responseText.includes("网页解析失败")) { logger.error(`服务器返回解析失败,可能URL或参数错误`); showCustomNotification("人脸识别失败", "服务器解析失败,URL或参数可能有误", "warning"); globalState.isProcessing = false; } else { logger.error(`解析人脸响应失败: ${e.message} (重试次数: ${retryCount})`); showCustomNotification("人脸识别失败", "响应解析错误,请查看控制台", "error"); handleRetry(params, retryCount); } } }, onerror: function(error) { logger.error(`人脸请求失败: ${error} (重试次数: ${retryCount})`); showCustomNotification("人脸请求失败", `网络错误: ${error}`, "error"); handleRetry(params, retryCount); } }); // 修改重试逻辑:保持参数不变,只重新获取图片和objectId async function handleRetry(originalParams, retryCount) { if (retryCount < maxRetries) { logger.info(`${retryDelay/1000}秒后进行第${retryCount + 1}次重试,将重新获取图片和objectId`); setTimeout(async () => { try { // 获取用户ID const puid = session.getCookie('_uid'); if (!puid) { throw new Error('未获取到用户ID,请重新登录'); } // 重新获取Token logger.info('重新获取上传Token...'); const uploadToken = await getUserTokenPromise(); // 重新获取人脸图片 logger.info('重新获取人脸图片数据...'); const imgBytes = await getUserFaceImageDataPromise(puid); // 重新处理图片 logger.info('重新处理人脸图片...'); const processedBytes = await processImageDataPromise(imgBytes); // 重新上传图片 logger.info('重新上传人脸图片...'); const newObjectId = await uploadFaceImagePromise(processedBytes, puid, uploadToken); // 使用原始参数和新的objectId重新提交 logger.info('使用新的objectId重新提交人脸识别...'); submitSign(originalParams, newObjectId, retryCount + 1); } catch (error) { logger.error(`重试过程失败: ${error.message}`); showCustomNotification('重试失败', error.message, 'error'); // 如果重试过程失败,继续下一次重试 if (retryCount + 1 < maxRetries) { handleRetry(originalParams, retryCount + 1); } else { logger.error("已达到最大重试次数,人脸识别失败"); showCustomNotification("人脸识别失败", "已达到最大重试次数", "error"); globalState.isProcessing = false; } } }, retryDelay); } else { logger.error("已达到最大重试次数,人脸识别失败"); showCustomNotification("人脸识别失败", "已达到最大重试次数", "error"); globalState.isProcessing = false; } } } // 修改autoProcessFaceRecognition函数,增加参数缓存 async function autoProcessFaceRecognition() { if (globalState.isProcessing) { logger.info('人脸识别正在处理中,跳过本次调用'); return; } globalState.isProcessing = true; logger.info('开始自动处理人脸识别'); try { // 1. 初始化Cookie initCookies(); const puid = session.getCookie('_uid'); if (!puid) { throw new Error('未获取到用户ID,请重新登录'); } // 2. 提取参数 const extractor = new FaceQRExtractor(); let params = extractor.getAllParams(); if (!params) { // 尝试从缓存中获取参数 try { const cachedParams = localStorage.getItem('lastValidParams'); if (cachedParams) { params = JSON.parse(cachedParams); logger.info('使用缓存的参数'); } } catch (e) { logger.error('获取缓存参数失败: ' + e.message); } if (!params) { logger.info('首次参数提取失败,等待2秒后重试'); await new Promise(resolve => setTimeout(resolve, 2000)); params = extractor.getAllParams(); if (!params) { throw new Error('未能提取到有效的QR码参数'); } } } // 缓存有效参数 try { localStorage.setItem('lastValidParams', JSON.stringify(params)); } catch (e) { logger.error('缓存参数失败: ' + e.message); } // 3. 验证必要参数 const requiredParams = ['courseid', 'clazzid', 'cpi', 'uuid']; const missingParams = requiredParams.filter(param => !params[param]); if (missingParams.length > 0) { throw new Error(`缺少必要参数: ${missingParams.join(', ')}`); } // 4. 获取Token logger.info('获取上传Token...'); const uploadToken = await getUserTokenPromise(); // 5. 获取人脸图片 logger.info('获取人脸图片数据...'); const imgBytes = await getUserFaceImageDataPromise(puid); // 6. 处理图片 logger.info('处理人脸图片...'); const processedBytes = await processImageDataPromise(imgBytes); // 7. 上传图片 logger.info('上传人脸图片...'); const objectId = await uploadFaceImagePromise(processedBytes, puid, uploadToken); // 8. 提交人脸识别 logger.info('提交人脸识别...'); submitSign(params, objectId); } catch (error) { logger.error('人脸识别处理失败: ' + error.message); showCustomNotification('人脸识别失败', error.message, 'error'); globalState.isProcessing = false; } } // 创建控制面板 function createControlPanel() { if (document.getElementById('faceRecognitionPanel')) { return; // 防止重复创建 } const panel = document.createElement('div'); panel.id = 'faceRecognitionPanel'; panel.style.cssText = ` position: fixed; top: 10px; left: 10px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none; border-radius: 12px; padding: 20px; z-index: 10000; box-shadow: 0 8px 32px rgba(0,0,0,0.3); font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; min-width: 320px; backdrop-filter: blur(10px); cursor: move; user-select: none; transition: all 0.3s ease; `; panel.innerHTML = `
🤖 超星人脸识别助手 v1.0.1
QQ群:164902340
人脸检测需要5-10秒,请耐心等待...
`; document.body.appendChild(panel); // 添加拖拽功能 let isDragging = false; let currentX; let currentY; let initialX; let initialY; let xOffset = 0; let yOffset = 0; const header = document.getElementById('panelHeader'); function dragStart(e) { if (e.type === "touchstart") { initialX = e.touches[0].clientX - xOffset; initialY = e.touches[0].clientY - yOffset; } else { initialX = e.clientX - xOffset; initialY = e.clientY - yOffset; } if (e.target === header || e.target === panel) { isDragging = true; panel.style.transition = 'none'; } } function dragEnd(e) { initialX = currentX; initialY = currentY; isDragging = false; panel.style.transition = 'all 0.3s ease'; } function drag(e) { if (isDragging) { e.preventDefault(); if (e.type === "touchmove") { currentX = e.touches[0].clientX - initialX; currentY = e.touches[0].clientY - initialY; } else { currentX = e.clientX - initialX; currentY = e.clientY - initialY; } xOffset = currentX; yOffset = currentY; // 限制拖拽范围在视窗内 const maxX = window.innerWidth - panel.offsetWidth; const maxY = window.innerHeight - panel.offsetHeight; currentX = Math.max(0, Math.min(currentX, maxX)); currentY = Math.max(0, Math.min(currentY, maxY)); panel.style.transform = `translate(${currentX}px, ${currentY}px)`; } } // 绑定拖拽事件 panel.addEventListener('mousedown', dragStart); document.addEventListener('mousemove', drag); document.addEventListener('mouseup', dragEnd); // 触摸设备支持 panel.addEventListener('touchstart', dragStart); document.addEventListener('touchmove', drag); document.addEventListener('touchend', dragEnd); // 绑定按钮事件 document.getElementById('extractParamsBtn').onclick = () => { const extractor = new FaceQRExtractor(); const params = extractor.getAllParams(); const display = document.getElementById('paramDisplay'); if (params) { display.innerHTML = `
${JSON.stringify(params, null, 2)}
`; display.style.display = 'block'; showCustomNotification('✅ 提取成功', '参数已显示在控制面板中', 'success'); } else { display.innerHTML = '
⚠️ 未找到新的QR码参数
'; display.style.display = 'block'; showCustomNotification('⚠️ 提取失败', '未找到新的QR码参数', 'error'); } }; document.getElementById('autoProcessBtn').onclick = () => { if (!globalState.isProcessing) { autoProcessFaceRecognition(); } else { showCustomNotification('⏳ 处理中', '人脸识别正在处理中,请稍候', 'warning'); } }; // 添加悬停效果 panel.addEventListener('mouseenter', () => { panel.style.transform += ' scale(1.02)'; panel.style.boxShadow = '0 12px 40px rgba(0,0,0,0.4)'; }); panel.addEventListener('mouseleave', () => { if (!isDragging) { panel.style.transform = panel.style.transform.replace(' scale(1.02)', ''); panel.style.boxShadow = '0 8px 32px rgba(0,0,0,0.3)'; } }); } // 检测人脸识别弹窗(防抖版本) const debouncedFaceDetection = debounce(() => { if (globalState.isProcessing) { return; } const now = Date.now(); if (now - globalState.lastProcessTime < 3000) { // 10秒内不重复处理 return; } // 检查当前页面URL,如果是visit/stucoursemiddle页面,直接判断为旧版人脸识别 const currentUrl = window.location.href; const isStudentCourseMiddlePage = currentUrl.includes('/visit/stucoursemiddle'); if (isStudentCourseMiddlePage) { // 在visit/stucoursemiddle页面直接触发旧版人脸识别处理 globalState.lastProcessTime = now; logger.info('检测到visit/stucoursemiddle页面,直接判断为旧版人脸识别,开始自动处理'); showCustomNotification('人脸识别检测', '检测到visit/stucoursemiddle页面,正在自动处理旧版人脸识别...', 'warning'); setTimeout(() => { autoProcessFaceRecognition(); }, 2000); return; } // 检测新版人脸识别 const newFaces = document.querySelectorAll(".chapterVideoFaceMaskDiv"); let newActive = false; for (const face of newFaces) { if (face.style.display !== "none") { newActive = true; break; } } // 检测旧版人脸识别 const oldFace = document.querySelector('.chapterVideoFaceQrMaskDiv'); const oldActive = oldFace && oldFace.style.display !== 'none'; if (newActive || oldActive) { globalState.lastProcessTime = now; const version = newActive ? '新版' : '旧版'; logger.info(`检测到${version}人脸识别弹窗,开始自动处理`); showCustomNotification('人脸识别检测', `检测到${version}人脸识别,正在自动处理...`, 'warning'); setTimeout(() => { autoProcessFaceRecognition(); }, 500); } }, 1000); // 统一的初始化函数 function init() { if (globalState.initialized) { return; } globalState.initialized = true; logger.info('超星人脸识别助手已启动'); // 请求通知权限 if (Notification.permission === 'default') { Notification.requestPermission(); } // 创建控制面板 createControlPanel(); // 设置人脸识别检测 const observer = new MutationObserver(debouncedFaceDetection); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style'] }); // 定期检测(备用机制) setInterval(() => { if (!globalState.isProcessing) { debouncedFaceDetection(); } }, 5000); logger.info('初始化完成,等待人脸识别触发'); } // 等待页面加载完成后初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();