// ==UserScript==
// @name tame QTKJ
// @namespace Vionlentmonkey
// @version 3.10.0
// @description at the end of with it.
// @author someone
// @icon https://www.skynj.com/theme/theme_443/images/sinosoft.ico
// @match http://218-94-1-181.sft.ipv6.jiangsu.gov.cn:8087/sfxzwsxy/*
// @match http://218-94-1-179.sft.ipv6.jiangsu.gov.cn:8087/sfxzwsxy/*
// @match http://218-94-1-175.sft.ipv6.jiangsu.gov.cn:8087/sfxzwsxy/*
// @match http://218.94.1.181:8087/sfxzwsxy/*
// @match http://218.94.1.179:8087/sfxzwsxy/*
// @match http://218.94.1.175:8087/sfxzwsxy/*
// @match http://218.94.1.181:5088/unzipapp/project/ware/attach/*
// @require https://openuserjs.org/src/libs/sizzle/GM_config.js
// @require https://greasyfork.org/scripts/410150-addstyle/code/addStyle.js
// @require https://greasyfork.org/scripts/410151-binary2text/code/binary2Text.js
// @require https://greasyfork.org/scripts/410152-fakenavigators/code/fakeNavigators.js
// @require https://greasyfork.org/scripts/410153-htmltoelements/code/htmlToElements.js
// @require https://greasyfork.org/scripts/395952-wsxy-autologin/code/wsxy_autoLogin.js
// @require https://greasyfork.org/scripts/395997-wsxy-windowtotab/code/wsxy_windowToTab.js
// @require https://greasyfork.org/scripts/396054-wsxy-storagedata/code/wsxy_storageData.js
// @require https://greasyfork.org/scripts/396002-wsxy-autolearn/code/wsxy_autoLearn.js
// @require https://greasyfork.org/scripts/395994-wsxy-autoplay/code/wsxy_autoPlay.js
// @require https://greasyfork.org/scripts/395966-wsxy-autoexaminetest/code/wsxy_autoExamineTest.js
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_openInTab
// @grant GM_notification
// @grant GM_download
// @grant unsafeWindow
// @run-at document-start
// ==/UserScript==
const windowCSS = `
#Cfg {
height: auto;
background-color: lightblue;
}
#Cfg .reset_holder {
float: left;
position: relative;
bottom: -1em;
}
#Cfg .saveclose_buttons {
margin: 1em;
}
`;
/**
* 打开设置选项
*/
const openCfg = () => {
// 避免在包含框架的页面打开造成多个设置界面重叠
if (window.top !== window.self) return;
GM_config.open();
};
// https://github.com/sizzlemctwizzle/GM_config/wiki#using-an-element-instead-of-an-iframe
const frame = document.createElement('div');
window.addEventListener('load', () => {
document.body.appendChild(frame);
});
GM_config.init({
id: 'Cfg',
title: '⚙个性化设置⚙',
frame,
fields: {
loginName: {
section: ['登录', '完整填写尝试自动登录'],
label: '账号',
labelPos: 'right',
type: 'text',
default: '',
},
pwd: {
label: '密码',
labelPos: 'right',
type: 'password',
default: '',
},
unlimited: {
label: '无限尝试',
labelPos: 'right',
type: 'checkbox',
default: false,
},
batch: {
section: ['操作', '如不计时请自行调低参数'],
label: '后台批量打开新标签页个数。疑似大于 6 无效。',
labelPos: 'right',
type: 'int',
default: 3,
},
muted: {
label: '静音播放',
labelPos: 'right',
type: 'checkbox',
default: true,
},
debug: {
section: ['开发者选项', '⚠随意开启,后果自负❗'],
label: '调试',
labelPos: 'right',
type: 'checkbox',
default: false,
},
},
css: windowCSS,
events: {
save: () => {
GM_config.close();
// 服务器选择页面或登录页面自动刷新
if (
location.pathname.match(/\/sfxzwsxy\/?(serverSelect.jsp)?#?$/i) ||
location.pathname.includes('index.jsp')
) {
location.reload(true);
}
},
},
});
const body = document.body || document.documentElement;
/**
* 彩蛋:通关密语
*/
let passPhrase = false;
if (localStorage.getItem('debug') === 'true') {
passPhrase = true;
}
/**
* 创建 Debug 选项
* @param {Element} parent_node
*/
const addDebugCheckbox = (parent_node) => {
debug_input = document.createElement('input');
debug_input.type = 'checkbox';
debug_input.id = 'debug';
if (localStorage.getItem('debug') === 'true') {
debug_input.checked = true;
} else {
debug_input.checked = false;
}
debug_input.onclick = () => {
localStorage.setItem('debug', `${document.getElementById('debug').checked}`);
if (localStorage.getItem('debug') === 'true') {
passPhrase = true;
} else {
passPhrase = false;
}
};
parent_node.appendChild(debug_input);
};
/**
* 创建设置按钮
* @param {Element} parent_node
*/
const addSettingButton = (parent_node) => {
setting_button = document.createElement('button');
setting_button.id = 'setting';
setting_button.textContent = '⚙个性化设置⚙';
setting_button.onclick = openCfg;
parent_node.appendChild(setting_button);
};
/**
* 创建批量打开按钮
* @param {Element} parent_node
*/
const addOpenButton = (parent_node) => {
open_button = document.createElement('button');
open_button.id = 'openTabs';
open_button.textContent = `批量打开${GM_config.get('batch')}个课程📖`;
open_button.onclick = openTrains;
parent_node.appendChild(open_button);
};
// 统一清理网址
uniformURLs();
// http://218.94.1.175:8087/sfxzwsxy/#
if (location.pathname.match(/\/sfxzwsxy\/?(serverSelect.jsp)?#?$/i)) {
console.log('serverSelect');
// 自动选择最空闲的服务器
if (GM_config.get('loginName') && GM_config.get('pwd')) {
setInterval(autoServerSelect, 1000);
} else {
// 若更早载入可能导致设置界面偏在右下角
window.addEventListener('load', () => {
addSettingButton(document.getElementsByClassName('title')[0]);
// 因必要信息不全,自动打开设置界面
openCfg();
});
}
}
// 登录页面
if (location.pathname.includes('index.jsp')) {
document.addEventListener('DOMContentLoaded', () => {
// 尝试自动登录
autoLogin();
});
// 进不去就刷新重来。GM_notification 的消息驻留事件为 15 秒。
window.addEventListener('load', () => {
setTimeout(() => {
location.assign(`${location.origin}/sfxzwsxy/serverSelect.jsp`);
}, 15500);
});
}
// 暴力覆盖登录时触发的 alert 函数
if (location.pathname.includes('login.jsp') || location.pathname.includes('index.jsp')) {
unsafeWindow.alert = (message) => {
console.log(message);
if (message === '密码错误,请重新输入!') {
GM_notification(
`${message}\n15 秒后重设。`,
'Alert: ❌',
'https://www.skynj.com/theme/theme_443/images/sinosoft.ico'
);
GM_config.set('pwd', '');
GM_config.save();
} else if (message === '该用户名不存在或已被删除,请重新输入!') {
// 可能是用户名输入错误;可能是尚未输入用户名就触发登录事件;可能是服务器过于拥堵。
GM_notification(
`${message}\n该提示有多种可能:\n若服务器空闲则大概率为用户名设置错误;\n若服务器拥堵则很可能为系统问题。\n点击🖱本消息清除已设置的用户名。\n否则将 15 秒一次循环♻重试。`,
'Alert',
'https://www.skynj.com/theme/theme_443/images/sinosoft.ico',
() => {
if (!GM_config.get('loginName')) return;
GM_config.set('loginName', '');
GM_config.save();
}
);
} else {
GM_notification(
message,
'Alert',
'https://www.skynj.com/theme/theme_443/images/sinosoft.ico'
);
}
};
}
// 主页
if (location.pathname.includes('index.html')) {
// 修复页面过长无法完整显示的 Bug; 隐藏 密码修改提示 和 每日一题
const css = `
html[style="overflow: hidden;"] {
overflow: visible !important;
}
#layui-layer1, #layui-layer-shade1, #layui-layer2, #layui-layer-shade2 {
display: none !important;
}
`;
addStyle(css);
/**
* 判断是否已经完成学年任务并处理
*/
const isFinish = () => {
// 主页直接获取页面值会快不少,避免与 iframe 同时抓取存储远程数据造成双重阻塞
// 规定需达到的总学时
const total_hour = Number(document.getElementById('totalHour').textContent);
// 规定需达到的必修学时
const required_hour = Number(document.getElementById('requiredHour').textContent);
// 规定需达到的总学分
const required_credit = Number(document.getElementById('requiredCredit').textContent);
// 用户已获得的总学时
const user_total_hour = Number(document.getElementById('userTotalHour').textContent);
// 用户已获得的必修学时
const user_required_hour = Number(document.getElementById('userRequiredHour').textContent);
// 用户已获得的总学分
const user_required_credit = Number(document.getElementById('userRequiredCredit').textContent);
if (
user_total_hour < total_hour ||
user_required_hour < required_hour ||
user_required_credit < required_credit
) {
// 收起导航,否则插入的批量打开按钮大概率无法在首屏显示。
if (document.querySelectorAll('a[class="cospull"]').length === 1) {
document.querySelector('a[class="cospull"]').click();
}
GM_notification(
`本学年任务尚未完成:`,
'',
'https://www.skynj.com/theme/theme_443/images/sinosoft.ico'
);
if (passPhrase) {
let mainframeSrc = document.getElementById('mainFrame').src;
if (!mainframeSrc.includes('homepage.jsp')) {
// 部分账号登录后显示“人员信息”界面,需要自动跳转到“首页导航”
document.querySelector('a[onclick*="homepage.jsp"]').click();
location.reload();
} else {
// 打开 iframe
GM_openInTab(mainframeSrc, true);
}
} else {
// 长时间不动会被弹出,故 30 分钟刷新一次
setInterval(() => {
location.reload(true);
}, 1800000);
}
if (user_total_hour < total_hour) {
GM_notification(
`总学时差:${(total_hour - user_total_hour).toFixed(1)}`,
'',
'https://www.skynj.com/theme/theme_443/images/sinosoft.ico'
);
}
if (user_required_hour < required_hour) {
GM_notification(
`必修学时差:${(required_hour - user_required_hour).toFixed(1)}`,
'',
'https://www.skynj.com/theme/theme_443/images/sinosoft.ico'
);
}
if (user_required_credit < required_credit) {
GM_notification(
`总学分差:${required_credit - user_required_credit}`,
'',
'https://www.skynj.com/theme/theme_443/images/sinosoft.ico'
);
}
} else {
GM_notification(
`本学年任务已经完成`,
'',
'https://www.skynj.com/theme/theme_443/images/sinosoft.ico'
);
}
};
window.addEventListener('DOMContentLoaded', () => {
// 在顶部⚙齿轮下用户名后添加设置按钮。
addSettingButton(document.getElementsByClassName('selexit-li1')[0]);
isFinish();
// 三管齐下避免误开启
if (GM_config.get('unlimited') && GM_config.get('muted') && GM_config.get('debug')) {
addDebugCheckbox(document.getElementsByClassName('topbar')[0]);
localStorage.setItem('debug', `${document.getElementById('debug').checked}`);
} else {
// 避免关闭选项后还保留数据
localStorage.removeItem('debug');
}
});
}
// 首页培训课程 iframe
if (location.pathname.includes('homepage.jsp')) {
window.addEventListener('load', async () => {
// 第一页 #courseExam1,第二页 #courseExam2,依此类推。
const exams = document.querySelectorAll('div[id^="courseExam"] > a[title]');
// 先将本地执行的非阻塞函数并发启动
Promise.all([
// 插入批量打开按钮
addOpenButton(document.getElementsByClassName('calschous')[0]),
// 新标签页打开考试
recoverExamList(exams),
// 新标签页打开题库
openKnowledge(),
]);
// 每次载入均阻塞获取数据。
await Promise.all([
await storageUserData(),
await storageCourseData(),
await storageCourseInfo(),
]);
// 获取待考试数据后才能添加答案链接。
const exam_courses = JSON.parse(localStorage.getItem('exam_courses'));
const waitCourseInfo = JSON.parse(localStorage.getItem('waitCourseInfo'));
addAnswer4ExamList(exams, exam_courses);
GM_notification(
'准备就绪✅',
'Fetch & Storage',
'https://www.skynj.com/theme/theme_443/images/sinosoft.ico'
);
passPhrase ? autoLearn(exams, waitCourseInfo) : autoSignupMaxCredit(waitCourseInfo);
});
}
// 培训课程查询 iframe
if (location.pathname.includes('course_query.jsp')) {
// 保证翻页生效
const document_observer = new MutationObserver(() => {
inquireList();
// 插入批量打开课程按钮
if (
document.getElementsByClassName('px-tits').length === 1 &&
// 防止多次插入
!document.getElementById('openTabs')
) {
addOpenButton(document.getElementsByClassName('px-tits')[0]);
}
});
document_observer.observe(body, {
childList: true,
subtree: true,
});
}
// 培训课程查询 - 查看 - 题干 iframe
if (
// 点击 “下一页” 或 “上一页” 后 iframe 实际地址会去除 .jsp 之后的尾巴
location.pathname.includes('subject_list.jsp')
) {
// 清理“题干”链接
document.addEventListener('DOMContentLoaded', () => {
viewSubject();
});
}
// 课程视频播放 跨域 iframe
if (location.href.startsWith('http://218.94.1.181:5088/unzipapp/project/ware/attach/')) {
// 旧播放器在 Windows 下要求 Flash,新播放器不兼容苹果系列。
fakeUA('Linux');
// 使用 MutationObserver 会导致无限刷新,对插入按钮函数的参数报错。
setInterval(autoPlay, 1000);
/**
* 创建下载按钮
* @param {Element} parent_node
*/
const addDownloadButton = (parent_node) => {
open_button = document.createElement('button');
open_button.id = 'startDownload';
open_button.textContent = `下载本视频▶`;
parent_node.appendChild(open_button);
};
window.addEventListener('load', () => {
addDownloadButton(document.getElementById('course_player'));
document.getElementById('startDownload').addEventListener('click', () => {
const url = document.getElementById('course_player5').src;
const title = document.title;
const ext = url.split('.').pop();
const name = `${title}.${ext}`;
console.log(url);
GM_download(url, name);
});
});
}
// 在线考试 - 课程考试 iframe
if (location.pathname.includes('course_exam_list.jsp')) {
// 清理“参加考试”链接,新标签页打开考试及答案。
document.addEventListener('DOMContentLoaded', () => {
const exams = document.querySelectorAll('a[href="#"][onclick^=openWindowFullScreen]');
const exam_courses = JSON.parse(localStorage.getItem('exam_courses'));
recoverExamList(exams);
addAnswer4ExamList(exams, exam_courses);
});
}
// 考试
if (location.pathname.includes('course_examine_test.jsp')) {
if (!passPhrase) return;
document.addEventListener('DOMContentLoaded', () => {
autoExamineTest();
});
}