职教云(智慧职教)网站:https://zjy2.icve.com.cn/
职教书签官网(开源):https://zj.miya.ink
此刷课脚本至目前(12月)仍然有效(因为职教云未重构课程进度计算的流程);
但是由于刷课人数过多,职教云现在会封禁学习速率过快的账号(从几十分钟到几小时不等),所以使用时常常会遇到:刷了一两个课,突然无法获取令牌的情况。如遇此情况请等待封禁结束,即可继续使用。
此脚本编写于4月份,在6月底公布;由于刷课速度无与伦比,使用至今肯定已被官方发觉,望各位学子耗子尾汁。
一句话简介:
一键刷完职教云(智慧职教)所有课程,随机生成学习时间。
支持所有类型:视频、ppt...等等
支持移动端,手机浏览器也能用
支持浏览器开多个标签同时刷多个课(只要每个页面都运行一遍职教书签即可)
2020年6月30日
更新:可以手动设置跳过已看多少进度的课程了
我需要做什么?
你无需安装任何浏览器插件,你需要做的只是把代码添加到浏览器书签(收藏夹),然后在课程页面单击运行它。
新功能补图(2020年6月30日):
输入跳过指定进度:
1、先上效果图
2、使用方法
将下面这行代码复制,添加到浏览器书签栏:
javascript:(function(){function getZjsq(){let s=document.createElement("script");s.src="https://zj.miya.ink/zjsq_v3.js";s.onerror=function(){alert("加载失败!请到职教书签官网(https://zj.miya.ink/)获取最新书签!")};document.body.appendChild(s)};getZjsq()}());
添加方法(如chrome):
打开书签管理器,添加新书签。
粘贴代码,保存。
添加完毕,之后就按照演示图那样运行即可。
3、原理
逆向分析职教云网页代码,通过运行javascript构造与职教云页面相同的请求,增加课程进度。
4、源代码
以下是职教书签v3的完整源代码,后续请在 https://zj.miya.ink 获取最新代码
使用源代码?
- 你可以将源代码复制,按F12打开浏览器开发者工具,在控制台中直接粘贴代码并回车。这等同于从浏览器书签运行职教书签。
- 如果提示无法从官网获取到书签,请像我上面说的这样做,即可继续使用了。
/*
职教书签v3
date: 2020年6月30日
version: 0.2.0
author: Pure-Peace
*/
// vars --------------------------------
const hrefs = []
const arrowDown = 'am-icon-caret-down'
const cl = 'class'
const sp = 'span'
const st = setTimeout
var zjsqInfoDom
var currentLessonIndex = 0
var lessonFailed = 0
var totalStudyTime = 0
var losingStreak = 0
var stopFlag = false
var skipProgress = 0
var totalLessons = 0
// funcs --------------------------------
function main () {
try {
const input = prompt('[职教书签v3] 跳过已学进度超过百分之几的课程?\n\n输入百分比,但是不要输入百分号\n如:90\n\n输入100或者更大的数字就不会跳过任何课程:', '90') || '100'
skipProgress = parseInt(input.replace(/[^0-9]/ig, '')) || 100
// fetch global datas
log('开始获取课件数据!')
globalDataHander()
// get datas
st(() => {
log('正在准备刷取学习进度及时间的必要信息...')
// started
directoryDataRequester(0)
}, 12000)
return 'started'
} catch (e) {
log('主程序异常,可能无法正常工作:' + e)
};
};
function log (text) {
const info = `[${new Date().format()}] ${text}`
console.log(info)
zjsqInfoDom.append(info + '<br>')
var ele = zjsqInfoDom[0]
ele.scrollTop = ele.scrollHeight + 999
};
function initial () {
// 请保持这种格式,否则getText函数无法从注释中正确提取此处的css
function zjsqCss () {/*
.zjsqInfoBox {
width: 700px;
height: 450px;
background-color:white;
position:absolute;
top:50%;
left:50%;
transform:translateX(-50%) translateY(-50%);
-moz-transform:translateX(-50%) translateY(-50%);
-webkit-transform:translateX(-50%) translateY(-50%);
-ms-transform:translateX(-50%) translateY(-50%);
border-radius:5px;
z-index: 9999;
box-shadow: 3px 3px 10px rgba(0,0,0,.2);
padding: 20px;
}
.zjsqTitle {
font-weight: bold;
font-size: 16px;
width: 100%;
text-align: center;
}
#zjysqInfo {
border-radius: 4px;
margin-top: 15px;
padding: 15px;
width: 100%;
height: 370px;
word-wrap: break-word;
overflow-y: scroll;
font-size: 14px;
color: #FAFAFA;
background-color: rgba(0,0,0,.8);
} */
};
// 请保持这种格式,否则getText函数无法从注释中正确提取此处的html
function zjsqHtml () {/*
<div id="zjsqInfoBoxId" class="zjsqInfoBox">
<div class="zjsqTitle">职教书签 v3 Beta2</div>
<div id="zjysqInfo">
-time-欢迎使用职教书签~! | 官网:<a target="_blank">https://zj.miya.ink</a>(开源)<br>
-time-开始初始化...请勿随意操作页面...<br>
</div>
</div> */
};
function getText (func) {
var str = func.toString().split('\n')
str = str.slice(1, str.length - 1).join('\n')
return str.replace(/-time-/g, `[${new Date().format()}] `)
};
function makeDivDraggable (id) {
var Drag = document.getElementById(id)
Drag.onmousedown = function (event) {
var ev = event || window.event
event.stopPropagation()
var disX = ev.clientX - Drag.offsetLeft
var disY = ev.clientY - Drag.offsetTop
document.onmousemove = function (event) {
var ev = event || window.event
Drag.style.left = ev.clientX - disX + 'px'
Drag.style.top = ev.clientY - disY + 'px'
Drag.style.cursor = 'move'
}
}
Drag.onmouseup = function () {
document.onmousemove = null
this.style.cursor = 'default'
}
};
try {
console.log('职教书签 v3 beta2 | 官网:https://zj.miya.ink')
console.log('开始初始化...请勿随意操作页面...')
Date.prototype.format = function () {
var format = 'yyyy-MM-dd HH:mm:ss'
var o = {
'M+': this.getMonth() + 1, // month
'd+': this.getDate(), // day
'H+': this.getHours(), // hour
'm+': this.getMinutes(), // minute
's+': this.getSeconds(), // second
'q+': Math.floor((this.getMonth() + 3) / 3), // quarter
S: this.getMilliseconds() // millisecond
}
if (/(y+)/.test(format)) {
format = format.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length))
};
for (var k in o) {
if (new RegExp('(' + k + ')').test(format)) {
format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length))
};
};
return format
}
$('<style></style>').text(getText(zjsqCss)).appendTo($('head'))
$('body').append(getText(zjsqHtml))
makeDivDraggable('zjsqInfoBoxId')
zjsqInfoDom = $('#zjysqInfo')
return true
} catch (e) {
log('初始化控制台框架异常:' + e)
return false
};
};
function hrefParamsToArray (url) {
return url
.substring(url.indexOf('?') + 1)
.split('&')
.map((query) => query.split('='))
.reduce((params, pairs) => (params[pairs[0]] = pairs[1] || '', params), {})
};
function studyProcessRequester (data) {
function getProcessText () {
return `[${new Date().format()}] 完成进度:(${totalCount}/${randomRequestTimes}) / 成功数:${successCount} / 失败数:${failedCount}`
};
if (stopFlag === true) return 0
var lessonId = `lesson${currentLessonIndex}`
var successCount = 0
var failedCount = 0
var totalCount = 0
var randomRequestTimes = Math.floor((Math.random() * 87) + 56)
const requestData = {
courseOpenId: data.courseOpenId,
openClassId: data.openClassId,
cellId: data.cellId,
cellLogId: data.cellLogId,
picNum: Math.round(324 / randomRequestTimes),
studyNewlyTime: Math.round(14640 / randomRequestTimes),
studyNewlyPicNum: Math.round(324 / randomRequestTimes),
token: data.guIdToken
}
log(`第(${currentLessonIndex}/${hrefs.length})课,课件:${data.cellName},类型:[${data.categoryName}]`)
log(`本次随机学习时间:${(randomRequestTimes * 10 / 60).toFixed(2)}分钟 总请求次数:${randomRequestTimes}`)
log('现在开始上课!')
zjsqInfoDom.append(`<div id="${lessonId}">${getProcessText()}</div>`)
var ele = zjsqInfoDom[0]
ele.scrollTop = ele.scrollHeight + 999
var lessonProcessDom = $(`#${lessonId}`)
for (let i = 0; i < randomRequestTimes; i++) {
var defer = $.Deferred()
$.ajax({
async: true,
timeout: 5000,
type: 'post',
url: urls2.Directory_stuProcessCellLog,
data: requestData,
dataType: 'json',
success: function (responseData) {
successCount += 1
},
error: function (response) {
failedCount += 1
},
complete: function (response) {
totalCount += 1
lessonProcessDom.text(getProcessText())
if (totalCount === randomRequestTimes) {
totalStudyTime += randomRequestTimes * 10
log(`当前课程(${lessonId}),已完成学习!三秒后开始下一课程...`)
st(function () {
return directoryDataRequester(currentLessonIndex)
}, 4000)
};
}
})
requestData.picNum += Math.round(300 / randomRequestTimes)
requestData.studyNewlyTime += Math.round(12640 / randomRequestTimes)
requestData.studyNewlyPicNum += Math.round(300 / randomRequestTimes)
};
return defer
};
function directoryDataRequester (hrefIndex, changeDirectory = false, addData = false) {
if (stopFlag === true) return 0
var changedFlag = false
if (hrefIndex < hrefs.length) {
currentLessonIndex = hrefIndex + 1
if (!addData && changeDirectory !== true) log(`正在获取课件(${currentLessonIndex}/${hrefs.length})的请求令牌...`)
var requestData = hrefParamsToArray(hrefs[hrefIndex])
if (addData) {
Object.assign(requestData, addData)
console.log(requestData)
delete (requestData.flag)
};
var defer = $.Deferred()
$.ajax({
async: true,
timeout: 5000,
type: 'post',
url: changeDirectory ? urls2.Directory_changeStuStudyProcessCellData : urls2.Directory_viewDirectory,
data: requestData,
dataType: 'json',
success: function (responseData) {
if (changeDirectory === true) {
log('课程切换成功!即将重新请求令牌...')
changedFlag = false
return directoryDataRequester(hrefIndex)
};
if (responseData.code === 1) {
log('令牌获取成功!准备就绪...')
losingStreak = 0
return studyProcessRequester(responseData)
} else if (responseData.code === -100) {
if (changedFlag === true) {
log('课程切换失败,将跳过此课程...')
failedHandler(responseData)
} else {
log('收到职教云提示切换课程...准备切换...')
changedFlag = true
changeDirectory = true
addData = {
cellName: responseData.currCellName,
moduleId: responseData.currModuleId
}
return directoryDataRequester(hrefIndex, changeDirectory, addData)
};
} else {
failedHandler(responseData)
};
},
error: function (response) {
log(`令牌获取失败!跳过此课程,直接开始下一课:(${currentLessonIndex})`)
console.log(response)
lessonFailed += 1
losingStreak += 1
if (losingStreak > 3) {
exitHander(-1)
} else {
directoryDataRequester(currentLessonIndex)
};
}
})
return defer
} else {
exitHander(1)
};
};
function exitHander (status) {
if (status === -1) {
stopFlag = true
const text = '由于令牌请求连续失败超过三次,所以书签将停止工作!请等待一段时间后再次使用!'
log(text)
alert(text)
};
const result = `本次共学习了${currentLessonIndex}个课件,成功数:${hrefs.length - lessonFailed},失败数:${lessonFailed},计算总学习时间约为:${(totalStudyTime / 60).toFixed(2)}分钟!`
log('**********学习结束!**********')
log(result)
if (status !== -1) alert('学习结束!' + result)
$('#zjsqInfoBoxId').click(function () {
$('#zjsqInfoBoxId').remove()
})
log('感谢您使用职教书签 v3!现在单击本窗口即可关闭。')
};
function globalDataHander () {
// get modules
log('正在获取课件模块数据(1/3)...')
$('.moduleList').each(function () {
const that = $(this).children('div').get(0)
if ($($(that).children(sp).get(1)).attr(cl).search(arrowDown) === -1) that.click()
})
// get children modules
st(() => {
log('正在获取课件详细数据(2/3)...')
$('tr.openOrCloseTopic').each(function () {
if ($($(this).find(sp).get(0)).attr(cl).search(arrowDown) === -1) $(this).click()
})
}, 3000)
// get links
st(() => {
log('正在获取所有课件链接(3/3)...')
$('a.isOpenModulePower').each(function () {
totalLessons += 1
if (skipProgress >= 100 || (parseInt($(this).prev().attr('title').replace(/[^0-9]/ig, '')) < skipProgress)) {
hrefs.push($(this).attr('data-href'))
};
})
log('已获取所有课件链接!课件总数:' + totalLessons)
log('根据您的设置,' + (skipProgress < 100 ? `将跳过学习进度大于${skipProgress}%的课程:跳过了${totalLessons - hrefs.length}课` : '本次将学习所有课程'))
log(`即将学习的课程数量为:${hrefs.length}`)
}, 8000)
};
// go
if (initial() === true) {
main()
} else {
alert('程序初始化异常,请查看控制台错误信息!')
};
5、更多想说的
可以识别已经看完的课程并跳过,但是由于我想重复刷取学习时间,并没有这么做。
你可以自行修改代码。
2020年6月30日
已更新:可以手动设置跳过已看多少进度的课程了
结束