// ==UserScript==
// @name 青书学堂API刷课助手 By:王杰
// @namespace https://github.com/barryallennnnn
// @version 1.0.3
// @description 青书学堂API刷课脚本,自动检测学校API地址
// @author lidppp
// @match *://*.qingshuxuetang.com/*
// @match *://*.qingshuxuetang.com.cn/*
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @connect *
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
const MAX_RETRY = 3;
const MAX_POSITION = 1000;
const REPORT_INTERVAL_MIN = 60000; // 最小60秒
const REPORT_INTERVAL_MAX = 90000; // 最大90秒
GM_addStyle(`
#qs-panel{position:fixed;top:20px;right:20px;width:380px;background:linear-gradient(135deg,#667eea,#764ba2);border-radius:12px;box-shadow:0 10px 40px rgba(0,0,0,.3);z-index:99999;font-family:system-ui,sans-serif;color:#fff;overflow:hidden}
.qs-hd{background:rgba(0,0,0,.2);padding:10px 14px;display:flex;justify-content:space-between;align-items:center;cursor:move}
.qs-hd h3{margin:0;font-size:14px}
.qs-hd button{background:none;border:none;color:#fff;font-size:16px;cursor:pointer}
.qs-bd{padding:14px;max-height:500px;overflow-y:auto}
.qs-tip{background:rgba(255,193,7,.2);border-left:3px solid #ffc107;padding:8px 10px;border-radius:0 6px 6px 0;margin-bottom:12px;font-size:11px;line-height:1.5}
.qs-gp{margin-bottom:10px}
.qs-gp label{display:block;margin-bottom:4px;font-size:12px;opacity:.9}
.qs-gp input,.qs-gp select{width:100%;padding:8px 10px;border:none;border-radius:6px;background:rgba(255,255,255,.9);color:#000;font-size:13px;box-sizing:border-box}
.qs-gp input:focus,.qs-gp select:focus{outline:none;background:rgba(255,255,255,.25)}
.qs-gp select option{background:#333;color:#fff}
.qs-row{display:flex;gap:8px}
.qs-btn{flex:1;padding:8px;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer}
.qs-btn-ok{background:#4caf50;color:#fff}
.qs-btn-no{background:#f44336;color:#fff}
.qs-btn-2{background:rgba(255,255,255,.2);color:#fff}
.qs-btn:disabled{opacity:.5;cursor:not-allowed}
.qs-stat{background:rgba(0,0,0,.15);border-radius:6px;padding:10px;margin:10px 0;font-size:12px}
.qs-task{background:rgba(0,0,0,.15);border-radius:6px;padding:8px 10px;margin:6px 0;font-size:11px}
.qs-task-ok{border-left:3px solid #4caf50}
.qs-task-err{border-left:3px solid #f44336}
.qs-task-run{border-left:3px solid #2196f3}
.qs-log{background:rgba(0,0,0,.2);border-radius:6px;padding:8px;margin-top:10px;max-height:120px;overflow-y:auto;font-family:monospace;font-size:10px;line-height:1.5}
.qs-log div{padding:1px 0}
.qs-t{opacity:.5;margin-right:6px}
.qs-i{color:#81d4fa}
.qs-ok{color:#81c784}
.qs-err{color:#e57373}
.qs-w{color:#ffd54f}
`);
function log(msg, type) {
type = type || 'i';
var el = document.getElementById('qs-log');
if (el) {
var t = new Date().toLocaleTimeString();
el.innerHTML += '
[' + t + ']' + msg + '
';
el.scrollTop = el.scrollHeight;
}
console.log('[青书刷课] ' + msg);
}
function getCookie(name) {
var m = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
return m ? m[2] : '';
}
function httpGet(url, cb) {
GM_xmlhttpRequest({
method: 'GET',
url: url,
headers: { 'Accept': 'application/json' },
timeout: 15000,
onload: function(r) {
try { cb(null, JSON.parse(r.responseText)); }
catch(e) { cb(null, null); }
},
onerror: function(e) { cb(e || new Error('error')); },
ontimeout: function() { cb(new Error('timeout')); }
});
}
function getToken() {
// 优先从输入框获取
var tokenEl = document.getElementById('qs-token');
if (tokenEl && tokenEl.value.trim()) {
return tokenEl.value.trim();
}
// 从cookie获取
var token = getCookie('AccessToken');
if (token) return token;
// 从localStorage获取
try {
token = localStorage.getItem('AccessToken') || localStorage.getItem('token') || '';
if (token) return token;
} catch(e) {}
// 尝试从页面脚本获取
try {
var scripts = document.querySelectorAll('script');
for (var i = 0; i < scripts.length; i++) {
var text = scripts[i].textContent || '';
var m = text.match(/token['":\s]*['"]([^'"]+)['"]/);
if (m && m[1]) return m[1];
}
} catch(e) {}
return '';
}
function getAllCookies() {
return document.cookie || '';
}
// 检查token是否过期
function isTokenExpired(token) {
if (!token || !token.includes('.')) return false;
try {
var parts = token.split('.');
if (parts.length === 3) {
var payload = JSON.parse(atob(parts[1]));
if (payload.exp) {
var expDate = new Date(payload.exp * 1000);
var now = new Date();
return now > expDate;
}
}
} catch(e) {}
return false;
}
function httpPost(url, data, cb) {
var token = getToken();
var allCookies = getAllCookies();
var headers = {
'Accept': 'application/json',
'Referer': window.location.href,
'User-Agent': navigator.userAgent
};
// 使用完整cookie
if (allCookies) {
headers['Cookie'] = allCookies;
}
// 添加AccessToken
if (token) {
headers['Cookie'] = (headers['Cookie'] || '') + '; AccessToken=' + token;
headers['Authorization'] = 'Bearer ' + token;
headers['X-Access-Token'] = token;
headers['token'] = token;
}
var opts = {
method: 'POST',
url: url,
headers: headers,
cookie: allCookies,
timeout: 30000,
onload: function(r) {
if (r.status === 200) {
try {
var json = JSON.parse(r.responseText);
cb(null, json);
}
catch(e) {
log('JSON解析失败', 'err');
cb(null, { hr: -1, message: '解析失败' });
}
} else if (r.status === 401) {
log('认证失败(401),请刷新页面重新登录', 'err');
cb(null, { hr: -1, message: 'HTTP 401 - 认证失败' });
} else {
log('HTTP错误: ' + r.status, 'err');
cb(null, { hr: -1, message: 'HTTP ' + r.status });
}
},
onerror: function(e) {
log('网络错误', 'err');
cb(e || new Error('error'));
},
ontimeout: function() {
log('请求超时', 'err');
cb(new Error('timeout'));
}
};
if (data instanceof FormData) {
opts.data = data;
} else {
opts.headers['Content-Type'] = 'application/json';
opts.data = JSON.stringify(data);
}
GM_xmlhttpRequest(opts);
}
// 自动检测学校API - 从当前页面URL推断
function detectSchoolApi(cb) {
var path = window.location.pathname;
log('当前路径: ' + path, 'i');
// 从路径中提取学校代码,例如 /tykj/Student/Course/CourseStudy -> /tykj/Student
var match = path.match(/^(\/[^\/]+\/[^\/]+)\//);
if (match && match[1]) {
var apiPath = match[1];
log('API路径: ' + apiPath, 'ok');
// 构造API地址
var apiBase = location.origin + apiPath;
log('API地址: ' + apiBase, 'ok');
document.getElementById('qs-api').value = apiBase;
cb(apiBase);
} else {
log('无法从路径推断API地址', 'w');
log('使用当前域名: ' + location.origin, 'i');
document.getElementById('qs-api').value = location.origin;
cb(location.origin);
}
}
// 测试API路径
function testApiPath(base, path, cb) {
var url = base + path;
log('测试: ' + url, 'i');
// 使用实际参数测试
var testParams = {
contentId: 'test',
contentType: 11,
courseId: '959',
teachPlanId: '558',
periodId: '25',
classId: '558'
};
httpPost(url, testParams, function(err, data) {
if (err) {
log('失败: ' + (err.message || '网络错误'), 'err');
cb(false);
} else {
log('响应: ' + JSON.stringify(data), 'i');
cb(true, data);
}
});
}
// 测试多个API路径
function findWorkingApiPath(base, cb) {
var paths = [
'/Course/UploadStudyRecordBegin',
'/Student/Course/UploadStudyRecordBegin',
'/Course/CourseData',
'/Student/Course/CourseData',
'/Course/UploadStudyRecord',
'/Student/Course/UploadStudyRecord',
'/Course/StudyRecord',
'/Student/Course/StudyRecord',
'/Course/SaveStudyRecord',
'/Student/Course/SaveStudyRecord',
'/Course/UpdateStudyRecord',
'/Student/Course/UpdateStudyRecord',
'/UploadStudyRecordBegin',
'/StudyRecordBegin',
'/SaveStudyRecordBegin'
];
var index = 0;
function testNext() {
if (index >= paths.length) {
log('所有路径都失败', 'err');
cb(null);
return;
}
var path = paths[index];
index++;
testApiPath(base, path, function(success, data) {
if (success && data && data.hr === 0) {
log('找到可用API: ' + base + path, 'ok');
cb(base + path);
} else {
testNext();
}
});
}
testNext();
}
// 检测课程
function detectCourse() {
if (!window.location.pathname.includes('CourseStudy')) return null;
var u = new URL(window.location.href);
var params = {
courseId: u.searchParams.get('courseId') || '',
teachPlanId: u.searchParams.get('teachPlanId') || '',
periodId: u.searchParams.get('periodId') || '',
classId: u.searchParams.get('classId') || u.searchParams.get('teachPlanId') || ''
};
if (!params.courseId) return null;
var nodes = [];
// 扩展选择器,支持更多页面结构
var selectors = [
'.left_part .level-1 a[id]',
'.course-tree a[href*="javascript"]',
'.chapter-list a[href*="javascript"]',
'.course-nav a[href*="javascript"]',
'.catalog-list a',
'.course-catalog a',
'a[href*="setContentId"]',
'a[onclick*="setContentId"]'
];
var links = [];
for (var s = 0; s < selectors.length; s++) {
var found = document.querySelectorAll(selectors[s]);
if (found.length > 0) {
links = found;
log('使用选择器: ' + selectors[s] + ' (找到' + found.length + '个)', 'i');
break;
}
}
for (var i = 0; i < links.length; i++) {
var href = links[i].getAttribute('href') || links[i].getAttribute('onclick') || '';
var m = href.match(/.*\('([^']+)'.*/);
if (!m || !m[1]) {
// 尝试其他匹配模式
m = href.match(/contentId[=:]\s*['"]?([^'"&]+)/i);
}
if (m && m[1]) {
// 多维度判断是否已学习
var isLearned = false;
var parent = links[i].closest('li') || links[i].parentElement;
// 检查各种可能的学习完成标记
if (links[i].querySelector('span.mark, span.learned, span.done, span.complete, i.icon-finish, .icon-complete')) {
isLearned = true;
}
if (parent && (parent.classList.contains('learned') || parent.classList.contains('completed') || parent.classList.contains('done'))) {
isLearned = true;
}
if (parent && parent.querySelector('span.mark, .learned-icon, .complete-icon, .finish-icon')) {
isLearned = true;
}
// 检查文本标记
var text = links[i].textContent || '';
if (text.includes('已学') || text.includes('已完成') || text.includes('✓') || text.includes('√')) {
isLearned = true;
}
nodes.push({
contentId: m[1],
contentType: 11,
courseId: params.courseId,
teachPlanId: params.teachPlanId,
periodId: params.periodId,
classId: params.classId,
learned: isLearned
});
}
}
// 去重
var uniqueNodes = [];
var seen = {};
for (var j = 0; j < nodes.length; j++) {
if (!seen[nodes[j].contentId]) {
seen[nodes[j].contentId] = true;
uniqueNodes.push(nodes[j]);
}
}
// 统计未学习的课件
var unlearned = uniqueNodes.filter(function(n) { return !n.learned; });
log('总课件: ' + uniqueNodes.length + ', 未学习: ' + unlearned.length, 'i');
return { params: params, nodes: unlearned, title: document.title };
}
// 任务类
function Task(base, params, onUpdate) {
this.base = base;
this.params = params;
this.onUpdate = onUpdate;
this.recordId = -1;
this.pos = 0;
this.retry = 0;
this.timer = null;
this.done = false;
}
Task.prototype.begin = function() {
if (this.done) return;
this.retry++;
if (this.retry > MAX_RETRY) {
this.onUpdate('fail', '重试次数用尽');
return;
}
var self = this;
var url = this.base + '/Course/UploadStudyRecordBegin';
log('POST ' + url, 'i');
log('参数: ' + JSON.stringify(this.params), 'i');
this.onUpdate('begin', '第' + this.retry + '次获取...');
httpPost(url, this.params, function(err, data) {
if (self.done) return;
if (err) {
log('请求失败: ' + (err.message || ''), 'err');
setTimeout(function() { self.begin(); }, 5000);
return;
}
log('响应: ' + JSON.stringify(data), 'i');
if (data.hr === 0) {
self.recordId = data.data;
log('recordId=' + self.recordId, 'ok');
self.retry = 0; // 重置重试计数
self.onUpdate('ready');
// 随机延迟20-40秒后开始上报(缩短等待时间)
var delay = Math.floor(Math.random() * 20000) + 20000;
log('等待 ' + Math.round(delay/1000) + ' 秒后开始上报', 'i');
setTimeout(function() { self.run(); }, delay);
} else if (data.message && data.message.includes('401')) {
log('认证失败,请检查Token', 'err');
self.onUpdate('fail', '认证失败');
} else {
log('失败: hr=' + data.hr + ' msg=' + (data.message || ''), 'err');
// 如果是其他错误,等待更长时间后重试
var retryDelay = self.retry * 3000;
setTimeout(function() { self.begin(); }, retryDelay);
}
});
};
Task.prototype.run = function() {
if (this.done) return;
this.onUpdate('run');
this.report();
};
Task.prototype.report = function() {
if (this.done) return;
var self = this;
// 每次增加80-150,加快完成速度
this.pos += Math.floor(Math.random() * 71) + 80;
if (this.pos >= MAX_POSITION) {
this.pos = MAX_POSITION; // 确保精确到1000
log('进度完成', 'ok');
this.end();
return;
}
var url = this.base + '/Course/UploadStudyRecordContinue?_t=' + Date.now();
log('上报: recordId=' + this.recordId + ' pos=' + this.pos, 'i');
// 使用FormData格式,与原始代码一致
var fd = new FormData();
fd.append('recordId', this.recordId);
fd.append('end', 'false');
fd.append('position', this.pos);
fd.append('timeOutConfirm', 'false');
httpPost(url, fd, function(err, resp) {
if (self.done) return;
if (err) {
log('上报错误: ' + (err.message || ''), 'err');
} else {
log('上报响应: ' + JSON.stringify(resp), 'i');
if (resp.hr === 0) {
log('进度 ' + self.pos + '/' + MAX_POSITION, 'ok');
} else {
log('上报失败: ' + (resp.message || 'hr=' + resp.hr), 'err');
// 如果失败,减少进度避免过快增长
self.pos = Math.max(0, self.pos - 30);
// 如果是频繁请求,等待更长时间
if (resp.message && resp.message.includes('频繁')) {
log('检测到频繁请求,等待2分钟后重试', 'w');
self.onUpdate('run', self.pos);
self.timer = setTimeout(function() { self.report(); }, 120000);
return;
}
}
}
self.onUpdate('run', self.pos);
if (!self.done) {
// 随机等待60-90秒后再次上报
var interval = Math.floor(Math.random() * (REPORT_INTERVAL_MAX - REPORT_INTERVAL_MIN)) + REPORT_INTERVAL_MIN;
log('等待 ' + Math.round(interval/1000) + ' 秒后再次上报', 'i');
self.timer = setTimeout(function() { self.report(); }, interval);
}
});
};
Task.prototype.end = function() {
var self = this;
var url = this.base + '/Course/UploadStudyRecordContinue?_t=' + Date.now();
// 使用FormData格式
var fd = new FormData();
fd.append('recordId', this.recordId);
fd.append('end', 'true');
fd.append('position', this.pos);
fd.append('timeOutConfirm', 'false');
httpPost(url, fd, function(err, resp) {
self.done = true;
if (err) {
log('结束请求失败', 'err');
} else {
log('结束响应: ' + JSON.stringify(resp), 'i');
}
self.onUpdate('done');
log('任务完成!', 'ok');
});
};
Task.prototype.stop = function() {
this.done = true;
clearTimeout(this.timer);
};
// 主应用
function App() {
this.course = null;
this.tasks = [];
this.running = false;
this.current = 0;
this.apiBase = '';
this.init();
}
App.prototype.init = function() {
this.createUI();
this.autoDetect();
// 延迟检测课程,等待页面DOM加载完成
var self = this;
setTimeout(function() { self.detect(); }, 1000);
// 再次延迟检测,确保动态内容加载完成
setTimeout(function() { self.detect(); }, 3000);
};
App.prototype.createUI = function() {
var div = document.createElement('div');
div.id = 'qs-panel';
div.innerHTML = '' +
'
青书学堂刷课助手 By:王杰
' +
'' +
'' +
'' +
'
自动检测学校API,进入课程页面后点击"开始刷课"。
' +
'
' +
'' +
'' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
未检测到课程
' +
'
' +
'' +
'' +
'
' +
'
' +
'' +
'' +
'' +
'
' +
'
' +
'
' +
'
';
document.body.appendChild(div);
// 拖拽
var hd = div.querySelector('.qs-hd');
var drag = false, sx, sy, sl, st;
hd.onmousedown = function(e) {
drag = true; sx = e.clientX; sy = e.clientY;
var r = div.getBoundingClientRect(); sl = r.left; st = r.top;
div.style.position = 'fixed'; div.style.left = sl + 'px'; div.style.top = st + 'px';
};
document.onmousemove = function(e) { if (drag) { div.style.left = (sl + e.clientX - sx) + 'px'; div.style.top = (st + e.clientY - sy) + 'px'; } };
document.onmouseup = function() { drag = false; };
var self = this;
document.getElementById('qs-min').onclick = function() { div.style.display = 'none'; };
document.getElementById('qs-start').onclick = function() { self.start(); };
document.getElementById('qs-stop').onclick = function() { self.stop(); };
document.getElementById('qs-refresh').onclick = function() { self.detect(); self.autoDetect(); };
document.getElementById('qs-gettk').onclick = function() { self.getToken(); };
document.getElementById('qs-save').onclick = function() { self.save(); };
document.getElementById('qs-test').onclick = function() { self.testApi(); };
document.getElementById('qs-debug').onclick = function() { self.debugAuth(); };
// 加载配置
var savedApi = GM_getValue('qs-api', '');
if (savedApi && savedApi.startsWith('http')) {
document.getElementById('qs-api').value = savedApi;
}
document.getElementById('qs-token').value = GM_getValue('qs-token', '');
log('脚本已加载', 'ok');
};
App.prototype.autoDetect = function() {
var self = this;
detectSchoolApi(function(url) {
self.apiBase = url;
document.getElementById('qs-api').value = url;
GM_setValue('qs-api', url);
});
};
App.prototype.detect = function() {
this.course = detectCourse();
var stat = document.getElementById('qs-stat');
var btn = document.getElementById('qs-start');
if (this.course) {
var total = this.course.nodes.length;
stat.innerHTML = '✓ ' + this.course.title + '
待学习课件: ' + total + ' 个';
btn.disabled = total === 0;
if (total > 0) {
log('检测到 ' + total + ' 个待学习课件', 'ok');
} else {
log('所有课件已学习完成!', 'ok');
}
} else {
stat.innerHTML = '✗ 未检测到课程页面';
btn.disabled = true;
}
};
App.prototype.getToken = function() {
var token = '';
// 尝试多种方式获取token
// 1. 从cookie获取
token = getCookie('AccessToken');
if (token) {
document.getElementById('qs-token').value = token;
log('从Cookie获取Token成功', 'ok');
return;
}
// 2. 从localStorage获取
try {
token = localStorage.getItem('AccessToken') || localStorage.getItem('token') || '';
if (token) {
document.getElementById('qs-token').value = token;
log('从localStorage获取Token成功', 'ok');
return;
}
} catch(e) {}
// 3. 尝试从页面元素获取
try {
var tokenInput = document.querySelector('input[name="token"], input[name="access_token"], #access_token, #token');
if (tokenInput && tokenInput.value) {
document.getElementById('qs-token').value = tokenInput.value;
log('从页面输入框获取Token成功', 'ok');
return;
}
} catch(e) {}
// 4. 尝试从页面脚本中提取
try {
var scripts = document.querySelectorAll('script');
for (var i = 0; i < scripts.length; i++) {
var text = scripts[i].textContent || '';
var patterns = [
/['"]?AccessToken['"]?\s*[:=]\s*['"]([^'"]+)['"]/i,
/['"]?token['"]?\s*[:=]\s*['"]([^'"]+)['"]/i,
/['"]?access_token['"]?\s*[:=]\s*['"]([^'"]+)['"]/i
];
for (var p = 0; p < patterns.length; p++) {
var m = text.match(patterns[p]);
if (m && m[1] && m[1].length > 10) {
document.getElementById('qs-token').value = m[1];
log('从页面脚本获取Token成功', 'ok');
return;
}
}
}
} catch(e) {}
log('无法自动获取Token,请手动输入', 'w');
log('提示: F12打开控制台,输入 document.cookie 查看', 'i');
};
App.prototype.save = function() {
var api = document.getElementById('qs-api').value.trim().replace(/\/+$/, '');
document.getElementById('qs-api').value = api;
GM_setValue('qs-api', api);
GM_setValue('qs-token', document.getElementById('qs-token').value.trim());
log('配置已保存', 'ok');
};
App.prototype.start = function() {
if (!this.course || !this.course.nodes || !this.course.nodes.length) return;
var base = document.getElementById('qs-api').value.trim().replace(/\/+$/, '');
if (!base) { log('请填写API地址', 'err'); return; }
// 检查token状态
var token = getToken();
if (!token) {
log('未找到Token,请先获取', 'err');
return;
}
// 检查token是否过期
if (token.includes('.')) {
try {
var parts = token.split('.');
if (parts.length === 3) {
var payload = JSON.parse(atob(parts[1]));
if (payload.exp) {
var expDate = new Date(payload.exp * 1000);
var now = new Date();
if (now > expDate) {
log('Token已过期,请刷新页面重新登录!', 'err');
log('过期时间: ' + expDate.toLocaleString(), 'err');
return;
}
}
}
} catch(e) {}
}
this.save();
this.running = true;
this.current = 0;
this.tasks = [];
document.getElementById('qs-start').disabled = true;
document.getElementById('qs-stop').disabled = false;
document.getElementById('qs-tasks').innerHTML = '';
log('开始刷课,共 ' + this.course.nodes.length + ' 个任务', 'ok');
this.processNext(base);
};
App.prototype.processNext = function(base) {
if (!this.running || this.current >= this.course.nodes.length) {
if (this.running) {
log('所有任务完成!', 'ok');
this.running = false;
document.getElementById('qs-start').disabled = false;
document.getElementById('qs-stop').disabled = true;
// 所有任务完成后刷新页面以更新状态
log('3秒后刷新页面更新状态...', 'i');
setTimeout(function() { location.reload(); }, 3000);
}
return;
}
var node = this.course.nodes[this.current];
var idx = this.current;
var self = this;
var div = document.createElement('div');
div.className = 'qs-task qs-task-run';
div.id = 'qs-t-' + idx;
div.textContent = '任务' + (idx + 1) + ': ' + node.contentId;
document.getElementById('qs-tasks').appendChild(div);
var task = new Task(base, node, function(status, pos) {
var el = document.getElementById('qs-t-' + idx);
if (!el) return;
if (status === 'done' || status === 'fail') {
el.className = 'qs-task ' + (status === 'done' ? 'qs-task-ok' : 'qs-task-err');
el.textContent = '任务' + (idx + 1) + ': ' + node.contentId + ' [' + (status === 'done' ? '完成' : '失败') + ']';
self.current++;
// 直接处理下一个任务,不刷新页面
setTimeout(function() { self.processNext(base); }, 2000);
} else {
el.textContent = '任务' + (idx + 1) + ': ' + node.contentId + ' [' + status + (pos !== undefined ? ' ' + pos + '/1000' : '') + ']';
}
});
this.tasks.push(task);
task.begin();
};
App.prototype.stop = function() {
this.running = false;
this.tasks.forEach(function(t) { t.stop(); });
document.getElementById('qs-start').disabled = false;
document.getElementById('qs-stop').disabled = true;
log('已停止', 'w');
};
App.prototype.testApi = function() {
var base = document.getElementById('qs-api').value.trim().replace(/\/+$/, '');
if (!base) {
log('请填写API地址', 'err');
return;
}
log('开始测试API路径...', 'i');
findWorkingApiPath(base, function(url) {
if (url) {
log('找到可用API: ' + url, 'ok');
} else {
log('未找到可用API', 'err');
}
});
};
App.prototype.debugAuth = function() {
log('=== 认证调试 ===', 'w');
// 1. 检查cookie
var allCookies = document.cookie;
log('Cookie内容: ' + (allCookies || '空'), 'i');
// 2. 检查AccessToken
var accessToken = getCookie('AccessToken');
log('AccessToken: ' + (accessToken || '无'), accessToken ? 'ok' : 'err');
// 3. 检查token过期
if (accessToken && accessToken.includes('.')) {
try {
var parts = accessToken.split('.');
if (parts.length === 3) {
var payload = JSON.parse(atob(parts[1]));
log('JWT解析成功', 'ok');
if (payload.exp) {
var expDate = new Date(payload.exp * 1000);
var now = new Date();
var isExpired = now > expDate;
log('过期时间: ' + expDate.toLocaleString(), isExpired ? 'err' : 'ok');
log('当前时间: ' + now.toLocaleString(), 'i');
if (isExpired) {
log('!!! Token已过期,请刷新页面重新登录 !!!', 'err');
} else {
var remain = Math.round((expDate - now) / 60000);
log('剩余时间: ' + remain + ' 分钟', 'ok');
}
}
}
} catch(e) {
log('JWT解析失败: ' + e.message, 'w');
}
}
// 4. 检查其他可能的token
var otherTokens = ['token', 'jwt', 'Authorization', 'X-Token'];
for (var i = 0; i < otherTokens.length; i++) {
var val = getCookie(otherTokens[i]);
if (val) {
log(otherTokens[i] + ': ' + val.substring(0, 20) + '...', 'i');
}
}
// 5. localStorage检查
try {
var lsToken = localStorage.getItem('AccessToken') || localStorage.getItem('token');
if (lsToken) {
log('localStorage token: ' + lsToken.substring(0, 20) + '...', 'i');
}
} catch(e) {}
log('================', 'w');
log('如Token过期,请刷新页面后重新登录', 'w');
};
// 启动
if (document.readyState === 'complete') {
new App();
} else {
window.addEventListener('load', function() { new App(); });
}
})();