// ==UserScript==
// @name tame QTKJ
// @namespace Vionlentmonkey
// @version 4.0.6
// @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/395997-wsxy-windowtotab/code/wsxy_windowToTab.js
// @require https://greasyfork.org/scripts/396054-wsxy-storagedata/code/wsxy_storageData.js
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_openInTab
// @grant GM_notification
// @grant unsafeWindow
// @grant window.close
// @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();
}
},
},
});
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);
};
/**
* 从 QQ 等打开地址后会被加上奇葩后缀
* http://218.94.1.179:8087/sfxzwsxy/#?tdsourcetag=s_pctim_aiomsg
*/
const uniformURLs = () => {
const tracks = [
// QQ
'?tdsourcetag=s_pctim_aiomsg',
// Weixin
'?from=groupmessage',
'&from=groupmessage',
'?from=singlemessage',
'&from=singlemessage',
'?from=timeline',
'&from=timeline',
'/type/WeixinReadCount',
];
for (const track of tracks) {
if (!location.href.endsWith(track)) continue;
location.href = location.href.replace(track, '');
}
/**
* 若不统一到对应 IP 上,打开课程等页面似乎会出错
* http://218-94-1-181.sft.ipv6.jiangsu.gov.cn:8087/sfxzwsxy/#?tdsourcetag=s_pctim_aiomsg
* http://218-94-1-179.sft.ipv6.jiangsu.gov.cn:8087/sfxzwsxy/#?tdsourcetag=s_pctim_aiomsg
* http://218-94-1-175.sft.ipv6.jiangsu.gov.cn:8087/sfxzwsxy/#?tdsourcetag=s_pctim_aiomsg
*/
if (!location.host.endsWith('.sft.ipv6.jiangsu.gov.cn:8087')) return;
location.host = location.host.replace('.sft.ipv6.jiangsu.gov.cn', '').split('-').join('.');
};
uniformURLs();
// http://218.94.1.175:8087/sfxzwsxy/#
if (location.pathname.match(/\/sfxzwsxy\/?(serverSelect.jsp)?#?$/i)) {
/**
* 自动选择最空服务器
*/
const autoServerSelect = () => {
const servers = document.getElementsByClassName('num');
if (servers.length === 0) return;
let servers_str = [];
let notificationText = '';
for (const s of servers) {
servers_str.push(s.textContent);
notificationText += `服务器${servers_str.length}使用程度:${s.textContent}%\n`;
}
// 将字符串元素转为数字
const servers_num = servers_str.map(Number);
// 全满则自动刷新,否则选择最空服务器
const mostFree = Math.min(...servers_num);
if (mostFree === 100) {
if (!GM_config.get('unlimited')) return;
// https://developer.mozilla.org/docs/Web/API/Location/reload
location.reload();
} else {
document.getElementsByClassName('entrybtn')[servers_num.indexOf(mostFree)].click();
GM_notification(
notificationText,
'自动选择最空闲的服务器!',
'https://www.skynj.com/theme/theme_443/images/sinosoft.ico'
);
}
};
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')) {
/**
* 在原函数的基础上,去掉验证码识别,去除 isBlank 函数依赖
*/
const check = () => {
if (document.getElementById('loginName').value === '') {
alert('请输入用户名');
document.getElementById('loginName').focus();
return;
}
if (document.getElementById('pwd').value === '') {
alert('请输入密码');
document.getElementById('pwd').focus();
return;
}
document.getElementById('form1').submit();
};
/**
* 去除登陆验证码校验
* 曾使用 OCR 识别法,参考了 https://www.cnblogs.com/ziyunfei/archive/2012/10/05/2710349.html 但准确度有限。
*/
const autoLogin = () => {
// 重新绑定点击事件
document.getElementById('Submit').onclick = check;
// 移除验证码并提示
document.getElementById('verifyCode').remove();
document.getElementById('imgCode').value = '已去除验证码可直接登录';
// 以下尝试自动登录
document.getElementById('loginName').value = GM_config.get('loginName'); // 写入预先设置的用户名
document.getElementById('pwd').value = GM_config.get('pwd'); // 写入预先设置的密码
// 自动获取用户名密码输入框焦点
if (document.getElementById('loginName').value === '') {
document.getElementById('loginName').focus();
} else if (document.getElementById('pwd').value === '') {
document.getElementById('pwd').focus();
} else {
// 用户名密码均已填写时才自动登录
document.getElementById('Submit').click();
}
};
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')) {
// 课程播放内嵌的 iframe 网址以 index.html 结尾
if (window.top !== window.self) return;
// 修复页面过长无法完整显示的 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);
let mainframeSrc = document.getElementById('mainFrame').src;
if (!mainframeSrc.includes('homepage.jsp')) {
// 部分账号登录后显示“人员信息”界面,需要自动跳转到“首页导航”
document.querySelector('a[onclick*="homepage.jsp"]').click();
location.reload();
} else 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'
);
// 长时间不动会被弹出,故 30 分钟刷新一次,为非全自动模式提供
setTimeout(() => location.reload(), 1800000);
if (passPhrase) {
// 打开 iframe
const iframeClose = GM_openInTab(mainframeSrc, true);
// 以防万一不能联动关闭,则 28 分钟自动强行关闭
setTimeout(iframeClose.close, 1680000);
// iframe 被关闭则自动刷新主页面
iframeClose.onclose = () => location.reload();
}
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')) {
/**
* 自动报名高学分课程。2020 年初,高于 1 学分的有且仅有 20 门 3 学分课程。
* @param {Array} waitCourseInfo
*/
const autoSignupMaxCredit = async (waitCourseInfo) => {
// 需要 iframe 提升才会执行
if (window.top !== window.self) return;
const courses = document.querySelectorAll('#requiredCourseTable .course');
for (const w of waitCourseInfo) {
/**
* 学分高且未报名
* 取消报名的也有 apply_pk,不能作为判断依据
* 但没有进度点数 jdpoint
*/
if (w.courseCredit <= 1 || w.jdpoint) continue;
console.log(w.course_name);
for (const c of courses) {
const coursePk = Number(c.getElementsByClassName('coursePk')[0].textContent);
if (coursePk !== w.course_pk) continue;
c.click();
const btn = document.getElementsByClassName('layui-layer-btn0');
if (btn.length !== 1) continue;
btn[0].click();
}
}
};
/**
* 自动报名高学时课程
* @param {Array} waitCourseInfo
*/
const autoSignupMaxTime = async (waitCourseInfo) => {
// 需要 iframe 提升才会执行
if (window.top !== window.self) return;
// 存储所有未报名课程的课时和对应编号
let timesMap = new Map();
for (const w of waitCourseInfo) {
// 报名后等于零,undefined 代表未报名
if (w.jdpoint !== undefined) continue;
timesMap.set(w.courseTime, w.course_pk);
}
const timesArray = [...timesMap.keys()];
const longest = Math.max(...timesArray);
console.log(`+${longest}h`);
const maxTimeCourse_pk = timesMap.get(longest);
const courses = document.querySelectorAll('#requiredCourseTable .course');
for (const c of courses) {
const coursePk = Number(c.getElementsByClassName('coursePk')[0].textContent);
if (coursePk !== maxTimeCourse_pk) continue;
c.click();
const btn = document.getElementsByClassName('layui-layer-btn0');
if (btn.length !== 1) continue;
btn[0].click();
}
};
/**
* 自动打开考试
* @param {NodeIterator} exams
*/
const autoOpenExam = (exams) => {
let i = 0;
let j = 0;
for (const exam of exams) {
const examURL = location.origin + '/sfxzwsxy/' + exam.getAttribute('onclick').split("'")[1];
const autoExam = GM_openInTab(examURL, true);
i++;
// 魔法打开的试卷全部关闭后自动关闭iframe,联动刷新主页面
autoExam.onclose = () => {
j++;
if (j === i) {
// https://www.tampermonkey.net/documentation.php#_grant
window.close();
}
};
// 魔法打开的考卷确认交卷后可能不能自动关闭,以防万一如此暴力处理,1分钟后强行关闭。
setTimeout(autoExam.close, 60000);
}
};
/**
* 自动打开待学习课程
* @param {Array} waitCourseInfo
*/
const autoOpenTrain = async (waitCourseInfo) => {
let i = 0;
let j = 0;
// 全部未完成必修课程。为同时解决学时学分问题,只关注必修课。
const courses = document.querySelectorAll('#requiredCourseTable .course');
for (const c of courses) {
const applyPk = Number(c.getElementsByClassName('applyPk')[0].textContent);
const jdjs = c.getElementsByClassName('jdjs')[0].textContent; // 完成进度定性
// 未报名课程 applyPk === '',取消报名的课程却能直接获取 applyPk,可能不适合使用 for length++ 循环
if (jdjs !== '完成进度') continue;
for (w of waitCourseInfo) {
if (w.apply_pk !== applyPk) continue;
const trainURL =
location.origin +
'/sfxzwsxy/jypxks/modules/train/ware/course_ware_view.jsp?applyPk=' +
applyPk +
'&courseType=1';
// https://github.com/violentmonkey/violentmonkey/issues/1194
await new Promise((r) => setTimeout(r, 10000));
const openClose = GM_openInTab(trainURL, false);
i++;
// 25 分钟自动关闭课程
setTimeout(openClose.close, 1500000);
// 魔法打开的课程关闭后自动关闭iframe,联动刷新主页面
openClose.onclose = () => {
j++;
if (j === i) {
// https://www.tampermonkey.net/documentation.php#_grant
window.close();
}
};
if (i >= GM_config.get('batch')) {
console.log(`已尝试批量打开${i}个课程`);
console.log('focus');
// 必须置于循环最后,否则 j 计数不准
return;
}
}
}
};
/**
* 自动学习的主函数
* @param {NodeIterator} exams
* @param {Array} waitCourseInfo
*/
const autoLearn = async (exams, waitCourseInfo) => {
if (window.top !== window.self) return;
// 30 分钟刷新一次
setInterval(() => {
location.reload();
}, 1800000);
const total_hour = Number(localStorage.getItem('total_hour')); //规定需达到的总学时
const required_hour = Number(localStorage.getItem('required_hour')); //规定需达到的必修学时
const required_credit = Number(localStorage.getItem('required_credit')); //规定需达到的总学分
const user_total_hour = Number(localStorage.getItem('user_total_hour')); //用户已获得的总学时
const user_required_hour = Number(localStorage.getItem('user_required_hour')); //用户已获得的必修学时
const user_required_credit = Number(localStorage.getItem('user_required_credit')); //用户已获得的总学分
console.log(`已获得:必修学时:${user_required_hour},学分:${user_required_credit}`);
// 判断是否已完成。首次在新标签页打开本页显然是未完成,但刷新后可能进入已完成状态。
if (
user_total_hour >= total_hour &&
user_required_hour >= required_hour &&
user_required_credit >= required_credit
) {
console.log(`本学年任务已经完成`);
return;
} else {
// 初始化预期学时/学分为已得值
let pendingCredit = user_required_credit;
let pendingTime = user_total_hour;
// 向预期学时/学分添加已报名课程数据
for (w of waitCourseInfo) {
// jdpoint 保证已报名,否则无法处理取消报名的问题
if (w.jdpoint >= 0) {
pendingCredit += w.courseCredit;
pendingTime += w.courseTime;
}
}
if (user_required_credit < required_credit && exams.length > 0) {
console.log('学分未满,有待考试课程');
autoOpenExam(exams);
// 以防万一不能自动关闭,则 1 分钟后考完关闭,1.5 分钟后刷新
setTimeout(() => {
location.reload();
}, 90000);
} else if (pendingTime < total_hour) {
console.log(
`已报名(不含待考试课程):必修学时:${pendingTime.toFixed(
1
)},学分:${pendingCredit},继续报名。`
);
autoSignupMaxTime(waitCourseInfo);
} else if (pendingTime >= total_hour && pendingCredit < required_credit) {
// 因为全部学习必修课,出现本状况可能很小,暂不处理
console.log(
`已报名(不含待考试课程):必修学时:${pendingTime.toFixed(
1
)},学分:${pendingCredit},有待处理。`
);
} else if (pendingTime >= total_hour && pendingCredit >= required_credit) {
console.log(
`已报名(不含待考试课程):必修学时:${pendingTime.toFixed(
1
)},学分:${pendingCredit},已达预期。`
);
if (user_required_hour < total_hour) {
console.log('学时未满,自动打开已报名课程,将定时关闭。');
autoOpenTrain(waitCourseInfo);
}
}
}
};
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/') &&
!location.href.endsWith('.mp4')
) {
// 旧播放器在 Windows 下要求 Flash,新播放器不兼容苹果系列。
fakeUA('Linux');
/**
* 使三类播放器均自动播放
* 旧版本播放器能否成功调用 HTML5 似乎是玄学问题,检测不到 HTML5 播放器则刷新。
*/
const autoPlay = () => {
// 自动从课程封面进入播放页面
if (document.querySelector('img[src="courseware/iconImg/z3.png"]')) {
document.querySelector('img[src="courseware/iconImg/z3.png"]').click();
}
// 学习进度超过 90% 和部分报错会以 alert 弹出。
unsafeWindow.alert = (message) => {
GM_notification(message, 'Alert');
console.log(message);
};
// 新原生 html5 播放器
const videoControlPanel = document.getElementsByClassName('videoControlPanel');
// 新播放器
const video_media = document.getElementById('video_media');
// 旧播放器
const html5Player = document.getElementById('course_player5');
if (video_media) {
/**
* 新播放器是否继续学习对话框调用 confirm,阻塞脚本运行。
* 测试例:http://218.94.1.175:8087/sfxzwsxy/jypxks/modules/train/ware/course_ware_view.jsp?applyPk=3063755&courseType=1
*/
unsafeWindow.confirm = (message) => {
if (message === '是否继续学习?') {
console.log(message);
return true;
} else {
GM_notification(message, 'Confirm');
console.log(message);
// 新旧播放器统一从头播放重新来过
return false;
}
};
/**
* 静音模式下自动播放无需用户授权
* https://developer.mozilla.org/docs/Web/Media/Autoplay_guide#Autoplay_availability
*/
if (GM_config.get('muted')) {
video_media.querySelector('video').muted = true;
}
/**
* https://developer.mozilla.org/docs/Web/Guide/Events/Media_events
* 此处不支持用 .next(clearInterval(...))
*/
video_media.querySelector('video').play();
} else if (html5Player) {
if (GM_config.get('muted')) {
html5Player.muted = true;
}
// 旧播放器是否继续学习对话框
if (document.getElementById('cancel')) {
//document.getElementById('confirm').click(); // 继续学习;可能需要多次重复才能完成该课程。
document.getElementById('cancel').click(); // 大侠还请重新来过
}
// 旧播放器自动做题
const ques = document.querySelectorAll('div.option > label > input[name="que"]');
if (ques.length > 0) {
// 兼容多选题
if (ques.length > 1) {
ques[1].click();
}
ques[0].click();
document.getElementsByClassName('button')[0].click(); // 提交
// 下一题
if (document.getElementsByClassName('button_xia').length === 1) {
document.getElementsByClassName('button_xia')[0].click();
}
// 完成
if (document.getElementsByClassName('button_wan').length === 1) {
document.getElementsByClassName('button_wan')[0].click();
}
}
// 此处支持 .next(clearInterval(...)),但不取消可以保证持续播放,即使用户点击页面也不影响。
html5Player.play();
} else if (videoControlPanel.length === 1) {
document.querySelector('video').muted = true;
document.querySelector('video').play();
} else if (!location.href.endsWith('.mp4')) {
// 若直接在新标签页打开mp4视频,id 为 media
location.reload();
}
};
// 使用 MutationObserver 会导致无限刷新,对插入按钮函数的参数报错。
setInterval(autoPlay, 1000);
/**
* 创建新标签打开视频文件按钮
* @param {Element} parent_node
*/
const addOpenInTabButton = (parent_node) => {
open_button = document.createElement('button');
open_button.id = 'openInTab';
open_button.textContent = `新标签页打开视频▶`;
parent_node.appendChild(open_button);
};
window.addEventListener('load', () => {
if (document.getElementById('course_player')) {
// 屏蔽透明gif层,避免遮挡所添加的按钮
addStyle('.sp-zz-l {display: none !important;}');
addOpenInTabButton(document.getElementById('course_player'));
document.getElementById('openInTab').addEventListener('click', () => {
const url = document.getElementById('course_player5').src;
GM_openInTab(url);
});
} else if (document.getElementsByClassName('mVideo').length === 1) {
// document.getElementById('video_media') 延迟产生
// 丑
addOpenInTabButton(document.querySelectorAll('tr.th')[0]);
document.getElementById('openInTab').addEventListener('click', () => {
const url = document.querySelector('video').src;
GM_openInTab(url);
});
} else if (document.getElementsByClassName('videoControlPanel').length === 1) {
// 丑
addOpenInTabButton(document.getElementsByClassName('menuTitle')[0]);
document.getElementById('openInTab').addEventListener('click', () => {
const url = document.querySelector('video').src;
GM_openInTab(url);
});
}
});
}
// 在线考试 - 课程考试 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;
/**
* 获取各题答案。一次性数据,无存储必要
* @param {String | Number} subjectPk
*/
const getSubjectData = async (subjectPk) => {
const subjectURL = `${location.origin}/sfxzwsxy//jypxks/modules/train/course/subject_view.jsp?subjectPk=${subjectPk}`;
const response = await fetch(subjectURL, {
method: 'POST',
body: 'blob',
});
const blob = await response.blob();
const subjectHtml = await binary2Text(blob);
const elements = htmlToElements(subjectHtml);
// https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map
let subjectDataMap = new Map();
// 题目类型:判断题/单选题/多选题
const questionType = elements
.querySelectorAll('table')[0]
.querySelectorAll('tr')[0]
.querySelectorAll('td')[1]
.textContent.trim();
subjectDataMap.set('questionType', questionType);
// 题目内容:string
const questionContent = elements
.querySelectorAll('table')[0]
.querySelectorAll('tr')[1]
.querySelectorAll('td')[1]
.textContent.trim();
// 判断题答案,选择题此处为空值
const judgementAnswer = elements
.querySelectorAll('table')[0]
.querySelectorAll('tr')[2]
.querySelectorAll('td')[1]
.textContent.trim();
subjectDataMap.set('questionContent', questionContent);
if (questionType === '判断题') {
subjectDataMap.set('judgementAnswer', judgementAnswer);
} else {
// 选择题答案表格第一行为标题:序号 选项内容 类型 是否为标准答案
// 此表格中答案选项与试题选项顺序打乱,序号没有意义,类型已获取也没有意义
const options = elements.querySelectorAll('table')[1].querySelectorAll('tr');
for (const option of options) {
const optionContent = option.querySelectorAll('td')[1].textContent.trim();
const optionAnswer = option.querySelectorAll('td')[3].textContent.trim();
if (optionContent === '选项内容' || optionAnswer === '是否为标准答案') continue;
subjectDataMap.set(optionContent, optionAnswer);
}
}
return subjectDataMap;
};
/**
* 打开考卷后自动答题交卷
*/
const autoExamineTest = async () => {
// 本考试所有试题
const topics = document.getElementsByClassName('topic-tms');
for await (const topic of topics) {
// 题号
const pkid = topic.querySelector('a[pkid]').getAttribute('pkid');
// 本题答案
const subjectDataMap = await getSubjectData(pkid);
// 本题选项
const options = topic.querySelectorAll('.tms-Right-wrong > p > a');
for (const option of options) {
const optionText = option.textContent.trim();
if (subjectDataMap.get('questionType') === '判断题') {
if (option.textContent.trim() !== subjectDataMap.get('judgementAnswer')) continue;
option.click();
} else {
// 选择题选项内容带着序号与空格,如“A ”,故获取第三个字符开始的子串
if (subjectDataMap.get(optionText.substring(2)) !== '是') continue;
option.click();
}
}
}
// 交卷
if (document.getElementsByClassName('subline _submit').length === 1) {
document.getElementsByClassName('subline _submit')[0].click();
}
// 确认
if (document.getElementsByClassName('layui-layer-btn0').length === 1) {
document.getElementsByClassName('layui-layer-btn0')[0].click();
}
};
document.addEventListener('DOMContentLoaded', () => {
autoExamineTest();
});
}