// ==UserScript==
// @name 免费中国大学Mooc答题助手(HappyMooc)
// @namespace http://tampermonkey.net/
// @version 1.3
// @description 免费答题助手(不需要购买题库)
// @author Your Name
// @match https://www.icourse163.org/learn/*
// @match http://www.icourse163.org/learn/*
// @match http://www.icourse163.org/spoc/learn/*
// @match https://www.icourse163.org/spoc/learn/*
// @match https://www.icourse163.org/mooc/*
// @grant GM.xmlHttpRequest
// @grant unsafeWindow
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const debug = false
const API_CONFIG = {
BASE_URL: debug?'http://localhost:5001':"https://mooc.gxx.cool",
ENDPOINTS: {
AID_CHANGE: '/api/aidChange',
USER_STATUS: '/api/user/status'
}
};
let qrcode = document.createElement('script');
qrcode.src = "https://cdn.bootcdn.net/ajax/libs/qrcodejs/1.0.0/qrcode.min.js";
document.head.appendChild(qrcode);
const buildUrl = (endpoint, params = {}) => {
let url = API_CONFIG.BASE_URL + endpoint;
Object.keys(params).forEach(key => {
url = url.replace(`{${key}}`, params[key]);
});
return url;
};
const user_info = unsafeWindow.webUser || GM_getValue('webUser');
const global_data = { webUser: { ...user_info }, courseDto: { ...unsafeWindow.courseDto }, curseData: {} };
console.log(global_data);
class ToastManager {
constructor() {
this.container = null;
this.toasts = [];
this.init();
}
init() {
this.addStyles();
this.createContainer();
}
addStyles() {
const style = document.createElement('style');
style.textContent = `
.toast-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 10000;
pointer-events: none;
}
.toast {
background: #fff;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
margin-bottom: 10px;
padding: 16px 20px;
min-width: 300px;
max-width: 400px;
pointer-events: auto;
transform: translateX(100%);
transition: all 0.3s ease;
border-left: 4px solid #007bff;
display: flex;
align-items: center;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
line-height: 1.4;
}
.toast.show {
transform: translateX(0);
}
.toast.success {
border-left-color: #28a745;
}
.toast.error {
border-left-color: #dc3545;
}
.toast.warning {
border-left-color: #ffc107;
}
.toast.info {
border-left-color: #17a2b8;
}
.toast-icon {
margin-right: 12px;
font-size: 18px;
flex-shrink: 0;
}
.toast.success .toast-icon::before {
content: '✓';
color: #28a745;
}
.toast.error .toast-icon::before {
content: '✕';
color: #dc3545;
}
.toast.warning .toast-icon::before {
content: '⚠';
color: #ffc107;
}
.toast.info .toast-icon::before {
content: 'ℹ';
color: #17a2b8;
}
.toast-content {
flex: 1;
}
.toast-title {
font-weight: 600;
margin-bottom: 4px;
color: #333;
}
.toast-message {
color: #666;
}
.toast-close {
margin-left: 12px;
background: none;
border: none;
font-size: 18px;
color: #999;
cursor: pointer;
padding: 0;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.2s ease;
}
.toast-close:hover {
background: #f0f0f0;
color: #666;
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 10001;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s ease;
}
.modal-overlay.show {
opacity: 1;
}
.modal {
background: #fff;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
max-width: 500px;
width: 90%;
max-height: 80vh;
overflow: hidden;
transform: scale(0.9);
transition: transform 0.3s ease;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.modal-overlay.show .modal {
transform: scale(1);
}
.modal-header {
padding: 20px 24px 16px;
border-bottom: 1px solid #eee;
display: flex;
align-items: center;
justify-content: space-between;
}
.modal-title {
font-size: 18px;
font-weight: 600;
color: #333;
margin: 0;
}
.modal-close {
background: none;
border: none;
font-size: 24px;
color: #999;
cursor: pointer;
padding: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.2s ease;
}
.modal-close:hover {
background: #f0f0f0;
color: #666;
}
.modal-body {
padding: 20px 24px;
color: #666;
line-height: 1.5;
}
.modal-footer {
padding: 16px 24px 20px;
border-top: 1px solid #eee;
display: flex;
justify-content: flex-end;
gap: 12px;
}
.btn {
padding: 8px 16px;
border-radius: 6px;
border: 1px solid #ddd;
background: #fff;
color: #333;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s ease;
}
.btn:hover {
background: #f8f9fa;
}
.btn-primary {
background: #007bff;
border-color: #007bff;
color: #fff;
}
.btn-primary:hover {
background: #0056b3;
border-color: #0056b3;
}
.btn-danger {
background: #dc3545;
border-color: #dc3545;
color: #fff;
}
.btn-danger:hover {
background: #c82333;
border-color: #c82333;
}
`;
document.head.appendChild(style);
}
createContainer() {
this.container = document.createElement('div');
this.container.className = 'toast-container';
document.body.appendChild(this.container);
}
show(type, title, message, duration = 4000) {
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.innerHTML = `
`;
this.container.appendChild(toast);
this.toasts.push(toast);
setTimeout(() => toast.classList.add('show'), 10);
const closeBtn = toast.querySelector('.toast-close');
closeBtn.addEventListener('click', () => this.remove(toast));
if (duration > 0) {
setTimeout(() => this.remove(toast), duration);
}
return toast;
}
remove(toast) {
toast.classList.remove('show');
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
const index = this.toasts.indexOf(toast);
if (index > -1) {
this.toasts.splice(index, 1);
}
}, 300);
}
success(title, message, duration) {
return this.show('success', title, message, duration);
}
error(title, message, duration) {
return this.show('error', title, message, duration);
}
warning(title, message, duration) {
return this.show('warning', title, message, duration);
}
info(title, message, duration) {
return this.show('info', title, message, duration);
}
confirm(title, message, onConfirm, onCancel) {
const overlay = document.createElement('div');
overlay.className = 'modal-overlay';
overlay.innerHTML = `
`;
document.body.appendChild(overlay);
setTimeout(() => overlay.classList.add('show'), 10);
const closeModal = () => {
overlay.classList.remove('show');
setTimeout(() => {
if (overlay.parentNode) {
overlay.parentNode.removeChild(overlay);
}
}, 300);
};
overlay.querySelector('.modal-close').addEventListener('click', () => {
closeModal();
if (onCancel) onCancel();
});
overlay.querySelector('.btn-cancel').addEventListener('click', () => {
closeModal();
if (onCancel) onCancel();
});
overlay.querySelector('.btn-confirm').addEventListener('click', () => {
closeModal();
if (onConfirm) onConfirm();
});
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
closeModal();
if (onCancel) onCancel();
}
});
}
}
const base64Encode = (str) => {
return btoa(unescape(encodeURIComponent(str)));
};
const toast = new ToastManager();
const createUI = () => {
const sidePanel = document.createElement('div');
Object.assign(sidePanel.style, {
position: 'fixed',
top: '20%',
right: '20px',
width: '220px',
backgroundColor: 'white',
border: '1px solid #e1e5e9',
borderRadius: '12px',
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.12)',
zIndex: '10000',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
overflow: 'hidden'
});
const tabContainer = document.createElement('div');
Object.assign(tabContainer.style, {
height: '60px',
backgroundColor: '#f8f9fa',
borderBottom: '1px solid #e9ecef',
display: 'flex',
alignItems: 'stretch'
});
const tab1 = document.createElement('div');
Object.assign(tab1.style, {
flex: '1',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
fontSize: '14px',
fontWeight: '500',
color: '#007bff',
backgroundColor: 'white',
borderBottom: '2px solid #007bff',
transition: 'all 0.3s ease'
});
tab1.textContent = '小程序码';
tab1.setAttribute('data-tab', 'miniprogram');
const tab2 = document.createElement('div');
Object.assign(tab2.style, {
flex: '1',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
fontSize: '14px',
fontWeight: '500',
color: '#666',
backgroundColor: '#f8f9fa',
borderBottom: '2px solid transparent',
transition: 'all 0.3s ease'
});
tab2.textContent = '绑定页面';
tab2.setAttribute('data-tab', 'binding');
const contentArea = document.createElement('div');
Object.assign(contentArea.style, {
padding: '16px',
backgroundColor: 'white',
minHeight: '200px'
});
const miniprogramContent = document.createElement('div');
Object.assign(miniprogramContent.style, {
textAlign: 'center'
});
const qrDescription = document.createElement('div');
Object.assign(qrDescription.style, {
fontSize: '12px',
color: '#666',
marginBottom: '16px',
lineHeight: '1.4'
});
qrDescription.textContent = '扫码进入小程序使用更多功能';
const miniprogramQRContainer = document.createElement('div');
Object.assign(miniprogramQRContainer.style, {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f8f9fa',
borderRadius: '8px',
border: '1px solid #e9ecef'
});
const qrImage = document.createElement('img');
Object.assign(qrImage.style, {
width: '150px',
height: '150px',
borderRadius: '8px',
backgroundColor: 'white',
objectFit: 'contain'
});
qrImage.src = 'https://gxx.cool/adnexa/suncode.png';
qrImage.alt = '小程序码';
qrImage.onerror = function() {
const placeholder = document.createElement('div');
Object.assign(placeholder.style, {
width: '200px',
height: '200px',
backgroundColor: '#e9ecef',
borderRadius: '8px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#999',
fontSize: '12px',
textAlign: 'center',
flexDirection: 'column'
});
placeholder.innerHTML = '小程序码加载失败
请检查网络连接
';
miniprogramQRContainer.replaceChild(placeholder, qrImage);
};
miniprogramQRContainer.appendChild(qrImage);
miniprogramContent.appendChild(qrDescription);
miniprogramContent.appendChild(miniprogramQRContainer);
const bindingContent = document.createElement('div');
Object.assign(bindingContent.style, {
display: 'none'
});
const bindingDescription = document.createElement('div');
Object.assign(bindingDescription.style, {
fontSize: '12px',
color: '#666',
textAlign: 'center',
marginBottom: '16px',
lineHeight: '1.4'
});
bindingDescription.textContent = '小程序扫描二维码完成绑定';
const bindingQRContainer = document.createElement('div');
Object.assign(bindingQRContainer.style, {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f8f9fa',
borderRadius: '8px',
border: '1px solid #e9ecef'
});
const encodedUserId = base64Encode(global_data.webUser.id);
console.log('生成的Base64编码userid:', encodedUserId);
const qrElement = document.createElement('div');
qrElement.style.display = 'inline-block';
const loadingHint = document.createElement('div');
Object.assign(loadingHint.style, {
padding: '30px',
fontSize: '12px',
color: '#666',
textAlign: 'center'
});
loadingHint.textContent = '正在生成二维码...';
qrElement.appendChild(loadingHint);
setTimeout(() => {
generateQRCode(qrElement, encodedUserId);
}, 3000);
bindingQRContainer.appendChild(qrElement);
bindingContent.appendChild(bindingDescription);
bindingContent.appendChild(bindingQRContainer);
const switchTab = (activeTab) => {
[tab1, tab2].forEach(tab => {
const isActive = tab.getAttribute('data-tab') === activeTab;
Object.assign(tab.style, {
color: isActive ? '#007bff' : '#666',
backgroundColor: isActive ? 'white' : '#f8f9fa',
borderBottomColor: isActive ? '#007bff' : 'transparent'
});
});
miniprogramContent.style.display = activeTab === 'miniprogram' ? 'block' : 'none';
bindingContent.style.display = activeTab === 'binding' ? 'block' : 'none';
};
tab1.addEventListener('click', (e) => {
e.stopPropagation();
switchTab('miniprogram');
});
tab2.addEventListener('click', (e) => {
e.stopPropagation();
switchTab('binding');
});
tabContainer.appendChild(tab1);
tabContainer.appendChild(tab2);
contentArea.appendChild(miniprogramContent);
contentArea.appendChild(bindingContent);
sidePanel.appendChild(tabContainer);
sidePanel.appendChild(contentArea);
document.body.appendChild(sidePanel);
addDragFunctionality(sidePanel);
};
const createBindingUI = () => {
const sidePanel = document.createElement('div');
Object.assign(sidePanel.style, {
position: 'fixed',
top: '40%',
right: '20px',
width: '320px',
backgroundColor: 'white',
border: '1px solid #e1e5e9',
borderRadius: '12px',
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.12)',
zIndex: '10000',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
overflow: 'hidden'
});
const tabContainer = document.createElement('div');
Object.assign(tabContainer.style, {
height: '60px',
backgroundColor: '#f8f9fa',
borderBottom: '1px solid #e9ecef',
display: 'flex',
alignItems: 'stretch'
});
const tab1 = document.createElement('div');
Object.assign(tab1.style, {
flex: '1',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
fontSize: '14px',
fontWeight: '500',
color: '#666',
backgroundColor: '#f8f9fa',
borderBottom: '2px solid transparent',
transition: 'all 0.3s ease'
});
tab1.textContent = '小程序码';
tab1.setAttribute('data-tab', 'miniprogram');
const tab2 = document.createElement('div');
Object.assign(tab2.style, {
flex: '1',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
fontSize: '14px',
fontWeight: '500',
color: '#007bff',
backgroundColor: 'white',
borderBottom: '2px solid #007bff',
transition: 'all 0.3s ease'
});
tab2.textContent = '绑定页面';
tab2.setAttribute('data-tab', 'binding');
const contentArea = document.createElement('div');
Object.assign(contentArea.style, {
padding: '16px',
backgroundColor: 'white',
minHeight: '200px'
});
const miniprogramContent = document.createElement('div');
Object.assign(miniprogramContent.style, {
textAlign: 'center',
display: 'none'
});
const qrTitle = document.createElement('div');
Object.assign(qrTitle.style, {
fontSize: '16px',
fontWeight: '600',
color: '#333',
marginBottom: '12px'
});
qrTitle.textContent = '小程序码';
const qrDescription = document.createElement('div');
Object.assign(qrDescription.style, {
fontSize: '12px',
color: '#666',
marginBottom: '16px',
lineHeight: '1.4'
});
qrDescription.textContent = '扫码进入小程序使用更多功能';
const miniprogramQRContainer = document.createElement('div');
Object.assign(miniprogramQRContainer.style, {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: '20px',
backgroundColor: '#f8f9fa',
borderRadius: '8px',
border: '1px solid #e9ecef'
});
const qrImage = document.createElement('img');
Object.assign(qrImage.style, {
width: '150px',
height: '150px',
borderRadius: '8px',
backgroundColor: 'white',
border: '1px solid #ddd',
objectFit: 'contain'
});
qrImage.src = 'https://gxx.cool/adnexa/suncode.png';
qrImage.alt = '小程序码';
qrImage.onerror = () => {
qrImage.style.display = 'none';
const errorPlaceholder = document.createElement('div');
Object.assign(errorPlaceholder.style, {
width: '150px',
height: '150px',
backgroundColor: '#f8f9fa',
borderRadius: '8px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#999',
fontSize: '12px',
textAlign: 'center',
border: '1px solid #e9ecef'
});
errorPlaceholder.textContent = '图片加载失败';
qrImage.parentNode.insertBefore(errorPlaceholder, qrImage.nextSibling);
};
miniprogramQRContainer.appendChild(qrImage);
miniprogramContent.appendChild(qrTitle);
miniprogramContent.appendChild(qrDescription);
miniprogramContent.appendChild(miniprogramQRContainer);
const bindingContent = document.createElement('div');
const bindingDescription = document.createElement('div');
Object.assign(bindingDescription.style, {
fontSize: '12px',
color: '#666',
textAlign: 'center',
marginBottom: '16px',
lineHeight: '1.4'
});
bindingDescription.textContent = '请使用微信小程序扫描下方二维码完成账户绑定';
const bindingQRContainer = document.createElement('div');
Object.assign(bindingQRContainer.style, {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
marginBottom: '16px',
padding: '12px',
backgroundColor: '#f8f9fa',
borderRadius: '8px',
border: '1px solid #e9ecef'
});
const encodedUserId = base64Encode(global_data.webUser.id);
console.log('生成的Base64编码userid:', encodedUserId);
const qrElement = document.createElement('div');
qrElement.style.display = 'inline-block';
const loadingHint = document.createElement('div');
Object.assign(loadingHint.style, {
padding: '30px',
fontSize: '12px',
color: '#666',
textAlign: 'center'
});
loadingHint.textContent = '正在生成二维码...';
qrElement.appendChild(loadingHint);
generateQRCode(qrElement, encodedUserId);
bindingQRContainer.appendChild(qrElement);
const debugInfo = document.createElement('details');
Object.assign(debugInfo.style, {
marginTop: '12px',
fontSize: '11px',
color: '#999'
});
debugInfo.innerHTML = `
显示二维码内容
${encodedUserId}
`;
bindingContent.appendChild(bindingTitle);
bindingContent.appendChild(bindingDescription);
bindingContent.appendChild(bindingQRContainer);
bindingContent.appendChild(debugInfo);
const switchTab = (activeTab) => {
[tab1, tab2].forEach(tab => {
const isActive = tab.getAttribute('data-tab') === activeTab;
Object.assign(tab.style, {
color: isActive ? '#007bff' : '#666',
backgroundColor: isActive ? 'white' : '#f8f9fa',
borderBottomColor: isActive ? '#007bff' : 'transparent'
});
});
miniprogramContent.style.display = activeTab === 'miniprogram' ? 'block' : 'none';
bindingContent.style.display = activeTab === 'binding' ? 'block' : 'none';
};
tab1.addEventListener('click', (e) => {
e.stopPropagation();
switchTab('miniprogram');
});
tab2.addEventListener('click', (e) => {
e.stopPropagation();
switchTab('binding');
});
tabContainer.appendChild(tab1);
tabContainer.appendChild(tab2);
contentArea.appendChild(miniprogramContent);
contentArea.appendChild(bindingContent);
sidePanel.appendChild(tabContainer);
sidePanel.appendChild(contentArea);
document.body.appendChild(sidePanel);
addDragFunctionality(sidePanel);
};
const generateQRCode = async (container, text) => {
try {
if (typeof QRCode === 'undefined') {
throw new Error('QRCode 库未加载,请确保已正确引入 qrcodejs');
}
console.log('检测到 QRCode 库,类型:', typeof QRCode);
container.innerHTML = '';
const qrContainer = document.createElement('div');
qrContainer.style.display = 'inline-block';
qrContainer.style.padding = '10px';
qrContainer.style.backgroundColor = '#ffffff';
qrContainer.style.borderRadius = '8px';
const qr = new QRCode(qrContainer, {
text: text,
width: 150,
height: 150,
colorDark: '#000000',
colorLight: '#ffffff',
correctLevel: QRCode.CorrectLevel.M
});
console.log('使用 qrcodejs 生成二维码成功');
container.appendChild(qrContainer);
} catch (error) {
console.error('二维码生成失败:', error);
console.log('切换到备用方案');
generateFallbackQRCode(container, text);
}
};
const generateFallbackQRCode = (container, text) => {
console.log('使用备用方案生成二维码');
container.innerHTML = '';
const canvas = document.createElement('canvas');
const size = 150;
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, size, size);
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, size, 20);
ctx.fillRect(0, 0, 20, size);
ctx.fillRect(size-20, 0, 20, size);
ctx.fillRect(0, size-20, size, 20);
const gridSize = 16;
const cellSize = Math.floor((size - 40) / gridSize);
const startX = 20;
const startY = 20;
ctx.fillStyle = '#000000';
for (let i = 0; i < gridSize; i++) {
for (let j = 0; j < gridSize; j++) {
const seed = text.charCodeAt((i * gridSize + j) % text.length);
if ((seed + i + j) % 3 === 0) {
ctx.fillRect(
startX + j * cellSize,
startY + i * cellSize,
cellSize - 1,
cellSize - 1
);
}
}
}
const cornerSize = cellSize * 3;
ctx.fillRect(startX, startY, cornerSize, cornerSize);
ctx.fillStyle = '#ffffff';
ctx.fillRect(startX + cellSize, startY + cellSize, cellSize, cellSize);
ctx.fillStyle = '#000000';
ctx.fillRect(startX + (gridSize - 3) * cellSize, startY, cornerSize, cornerSize);
ctx.fillStyle = '#ffffff';
ctx.fillRect(startX + (gridSize - 2) * cellSize, startY + cellSize, cellSize, cellSize);
ctx.fillStyle = '#000000';
ctx.fillRect(startX, startY + (gridSize - 3) * cellSize, cornerSize, cornerSize);
ctx.fillStyle = '#ffffff';
ctx.fillRect(startX + cellSize, startY + (gridSize - 2) * cellSize, cellSize, cellSize);
const qrContainer = document.createElement('div');
qrContainer.style.display = 'inline-block';
qrContainer.style.padding = '10px';
qrContainer.style.backgroundColor = '#ffffff';
qrContainer.style.borderRadius = '8px';
qrContainer.style.border = '1px solid #ddd';
qrContainer.appendChild(canvas);
container.appendChild(qrContainer);
const hint = document.createElement('div');
hint.style.marginTop = '10px';
hint.style.fontSize = '12px';
hint.style.color = '#999';
hint.innerHTML = '请使用微信扫一扫 ⚠️ 备用显示';
container.appendChild(hint);
};
const checkUserStatus = () => {
return new Promise((resolve) => {
if (!global_data.webUser?.id) {
console.log('用户未登录,无法检查状态');
return resolve(false);
}
GM.xmlHttpRequest({
method: 'GET',
url: `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.USER_STATUS}?userid=${global_data.webUser.id}`,
headers: { 'Content-Type': 'application/json' },
timeout: 3000,
onload: (response) => {
console.log(response);
const isEnabled = handleStatusResponse(response);
resolve(isEnabled);
},
onerror: (error) => {
console.error('检查用户状态网络错误:', error);
resolve(true);
}
});
});
};
const handleStatusResponse = (response) => {
if (response.status !== 200) {
console.error('检查用户状态请求失败:', response.status);
return false;
}
try {
const result = JSON.parse(response.responseText);
if (result.status !== 0) {
console.error('检查用户状态失败:', result.msg);
return false;
}
const userData = result.data;
console.log('用户状态检查结果:', userData);
global_data.webUser.is_banned = userData.is_banned;
global_data.webUser.is_bound = userData.is_bound;
if (userData.is_banned) {
console.log('用户已被封禁,禁止使用脚本');
return false;
}
console.log('用户状态正常,允许使用脚本');
return true;
} catch (error) {
console.error('解析用户状态响应失败:', error);
return false;
}
};
const reportAidChange = () => {
GM.xmlHttpRequest({
method: 'POST',
url: buildUrl(API_CONFIG.ENDPOINTS.AID_CHANGE),
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify({
userID: global_data.webUser.id,
aid: global_data.curseData.aid,
tid: global_data.curseData.tid,
courseName: global_data.courseDto.name,
testName: global_data.curseData.tname,
isExam: global_data.curseData.isExam
}),
onload: (response) => {
if (response.status === 200) {
console.log('AID change reported successfully:', response.responseText);
toast.success('记录成功', '已同步打开试卷信息');
} else {
console.error('Failed to report AID change:', response);
toast.error('记录失败', '');
}
},
onerror: (error) => {
console.error('Error reporting AID change:', error);
toast.error('网络错误', '');
}
});
};
const hookXHR = () => {
const originalXHR = unsafeWindow.XMLHttpRequest;
unsafeWindow.XMLHttpRequest = class extends originalXHR {
open(method, url, ...rest) {
this._intercept = url.includes('getOpenQuizPaperDto')||url.includes('getOpenHomeworkPaperDto');
super.open(method, url, ...rest);
}
set onreadystatechange(callback) {
super.onreadystatechange = () => {
if (this._intercept && this.readyState === 4) {
try {
const jsonResponse = JSON.parse(this.responseText);
if (jsonResponse.result && jsonResponse.result.aid) {
global_data.curseData.aid = jsonResponse.result.aid;
global_data.curseData.tid = jsonResponse.result.tid;
global_data.curseData.tname = jsonResponse.result.tname;
global_data.curseData.isExam = !!jsonResponse.result.examId && jsonResponse.result.examId !== -1;
console.log('提取到的数据:', {
aid: global_data.curseData.aid,
tid: global_data.curseData.tid,
courseName: global_data.courseDto.name,
testName: global_data.curseData.tname,
examId: jsonResponse.result.examId,
isExam: global_data.curseData.isExam
});
reportAidChange();
}
} catch (error) {
console.error('Failed to parse response as JSON:', error);
}
}
callback?.call(this);
};
}
};
};
const addDragFunctionality = (element) => {
let isDragging = false;
let currentX;
let currentY;
let initialX;
let initialY;
let xOffset = 0;
let yOffset = 0;
const dragHandle = element.children[0];
if (!dragHandle) return;
const rect = element.getBoundingClientRect();
xOffset = rect.left;
yOffset = rect.top;
dragHandle.style.cursor = 'move';
dragHandle.style.userSelect = 'none';
dragHandle.addEventListener('mousedown', dragStart);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', dragEnd);
function dragStart(e) {
e.stopPropagation();
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
if (e.target === dragHandle || dragHandle.contains(e.target)) {
isDragging = true;
element.style.transition = 'none';
element.style.left = xOffset + 'px';
element.style.top = yOffset + 'px';
element.style.right = 'auto';
}
}
function drag(e) {
if (isDragging) {
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
xOffset = currentX;
yOffset = currentY;
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
const elementWidth = element.offsetWidth;
const elementHeight = element.offsetHeight;
if (currentX < 0) currentX = 0;
if (currentX > windowWidth - elementWidth) currentX = windowWidth - elementWidth;
if (currentY < 0) currentY = 0;
if (currentY > windowHeight - elementHeight) currentY = windowHeight - elementHeight;
element.style.left = currentX + 'px';
element.style.top = currentY + 'px';
element.style.right = 'auto';
}
}
function dragEnd(e) {
if (isDragging) {
initialX = currentX;
initialY = currentY;
isDragging = false;
element.style.transition = 'all 0.2s ease';
}
}
};
let aid = null;
let tid = null;
checkUserStatus().then(isEnabled => {
if (isEnabled) {
createUI();
hookXHR();
GM_setValue('webUser', global_data.webUser);
setTimeout(() => {
toast.success('脚本已加载', '慕课助手已成功加载!', 3000);
}, 1000);
} else {
if (global_data.webUser && global_data.webUser.is_banned) {
console.log('用户已被封禁,脚本不会加载');
toast.error('账户被禁用', '您的账户已被禁用,无法使用此功能。如有疑问请联系管理员。', 0);
} else if (global_data.webUser && global_data.webUser.is_bound === false) {
console.log('用户未绑定,显示绑定二维码');
createBindingUI();
toast.info('需要绑定', '请扫描二维码完成账户绑定', 0);
} else {
console.log('用户状态异常,脚本不会加载');
toast.error('状态异常', '用户状态异常,请检查登录状态。', 0);
}
}
});
})();