Skip to main content

某习通小助手

· 27 min read
Kuizuo

前言

声明,本文与该软件仅用于学习技术交流,请勿将软件用于非法途径,本作者不承担一切法律责任,望使用者须知。若影响到贵公司正常运行,请联系本人删除。

注:本文适用于有编程基础的,会 Http 请求那更好。

使用语言

有人会很好奇这个软件到底是怎么运行的,为啥可以实现自动完成视频,作业。借此写个完整的该软件开发过程,供各位学习,整个开发过程真不算的难,听我慢慢道来(尽可能详细),但你看完后,写不出来,大概率也不会想写

既然是写软件,那怎么能不说说编程语言,首先这个软件是基于易语言开发的,初学易语言的三个月所写的练手项目,本是写来给我自用的,不过确实好用,那为啥不分享出去呢。

实际上我也考虑过开源,奈何多次审核不通过,累了,就懒得开源了。

首先,说说为啥会选择易语言,有一部分原因是因为我那时候正好在学易语言,哪怕现在如果要开发一个类似于这样的软件,我也会优选易语言(在不考虑兼容与报毒情况下)。原因其实非常简单,好写,太好写了,我记得那时候的第一版超星刷课只花了一周时间,实现了自动完成视频,那还只是我没什么开发经验的前提下(借鉴了外面的一份源码)。再者也是最主要的一部分,写的软件是基于什么平台,很显然,桌面级应用 Windows 平台。那就少不了交互界面了,而正是这个交互界面,让我劝退了 javascrpit 与 python。不是说他们不行,而是写起来绝对比易语言复杂。如果你有接触过这两者相关的估计会知道,尤其还是实现自动完成任务的功能,基本上是不提供界面而言。也就让代码的可操作性少了非常多,这还不是最致命的,致命的是使用者不是人人都学程序了,即使发你一个 python 文件,但他大概率是不会听你大费周章的安装,输入,原因就是麻烦(如果这份代码好用的话当我没说过)。

说这些都不如直接来一个 exe 可执行文件,让用户去点击操作,然后通过一个日志输出显示给用户,告知用户当前程序执行进度。可能有人又会问,那为啥不用 C#,VB.net,QT 等,我 tm 要是会的话,也不会用易语言来写了,易语言敲代码体验很差,如果用过其他的文本编辑器,就特别不想用易语言(反正我是这样,真的难用),毕竟易语言都是 20 年前的产物了,能活到现在就不错了。但不得不说,易语言是真的好写,好用,好上手。

语言不分贵贱,能写出好的程序都是好语言,所以本文都是以本人从易语言开发角度来讲述,如果你恰好有程序开发经验,或有想接触的,本文或许能给予你一些帮助。

找源码

既然介绍完所用语言,那么就开始编写代码吧,不过在此之前先别急,这一步尤为关键,能极大的节省你所开发的效率,那就是搜索是否的相关源码或者软件。最好是与自己所开发的语言一致。

这是我当时编写软件前在吾爱破解论坛上搜索到的相关源码,如下:image-20200925173801271

看看软件源码的界面image-20200925173832002

在比对一下我的修改了数十次的。

image-20201220072514909

没错,我就是基于这个软件改的,还是有点相似之处的。但事实上这个原作者的代码在我翻阅到时就已经不能用了,并且还有很多弊端,例如还需要输入学校名称,输入验证码,这对用户体验来说的是非常烦人的。

同时在这份源码上只能说是一份临时品,几乎没有维护可言(虽然易语言写的软件多半都不好维护),不过有一个核心加密算法,也就是最终提交视频的一个核心算法,让我省去 JS 逆向分析的时间(后文会说到,不过以我那时候来看,这个 JS 自行解决也不成问题)

那时候搜索到的还有其他的相关脚本,例如大多数人都了解过的油猴插件。后文有简单讲述到,因为和本文涉及到的不相关。

执行流程

找到相关源码或软件,就已经离项目完成快了一半了,接着只需要在该软件上进行修改,已达到自己的目的。当然,如果要补充一些功能还需要花费很多时间的。

页面设计

我优先做的就是修改 UI 界面,做到竟可能的不丑,且符合个人风格。而这部分就是拖拽组件,移动组件,微调组件,平行垂直等操作,没啥可言的。我所用的都是 windows 自带的组件,加上我不会自绘组件,只好借助皮肤模块来美化界面了。美化的效果如下图

image-20201226062612827

实际上,页面设计相关就到此结束了,我能做的也只是尽量不丑,毕竟不会自绘组件,用原生自带的组件就这样了。当然,后面关于怎么数据渲染到组件这些会写到的。

登录

接着就是要说实现原理,首先回想一下,我们如果手动去一个个看视频,答题,需要干嘛,那肯定是登录了,不登录学习通那边怎么知道是你,那么在浏览器中,登录只是输入下账号,密码,然后点击登录按钮就完事了。然而实际原理不只是点击按钮这么简单,实则是发送一个 http 请求给后端,后端进行效验结果比对,返回结果,我简单叙述一下,放 js 代码来看看:

image-20201226063607140

具体看图片

image-20201226064003045

完整关键代码如下:(已删除不必要代码)

//手机号+密码登录
function loginByPhoneAndPwd() {
var phone = $('#phone').val().trim()
var pwd = $('#pwd').val()
var fid = $('#fid').val()
var refer = $('#refer').val()
if (util.isEmpty(phone)) {
util.showMsg(true, 'phoneMsg', '请输入手机号', true)
return
}
if (util.isEmpty(pwd)) {
util.showMsg(true, 'pwdMsg', '请输入密码', true)
return
}
var t = $('#t').val()
if (t == 'true') {
pwd = $.base64.btoa(pwd, 'UTF-8')
}
// --------------------------------------------------------
$.ajax({
url: '/fanyalogin',
type: 'post',
dataType: 'json',
data: {
fid: fid,
uname: phone,
password: pwd,
refer: refer,
t: t,
},
success: function (data) {
if (data.status) {
var url = ''
if (data.tochaoxing) {
var path = window.location.protocol + '//' + window.location.host
url =
path +
'/towriteother?name=' +
encodeURIComponent(data.name) +
'&pwd=' +
encodeURIComponent(data.pwd) +
'&refer=' +
data.url
} else {
url = decodeURIComponent(data.url)
}

if (top.location != self.location && $('#_blank').val() == '1') {
top.location = url
} else {
window.location = url
}
} else {
if (data.weakpwd) {
window.location =
'/v11/updateweakpwd?uid=' +
data.uid +
'&oldpwd=' +
encodeURIComponent($('#pwd').val()) +
'&refer=' +
refer
} else {
var msg = util.isEmpty(data.msg2) ? '登录失败' : data.msg2
msg = '密码错误' == msg || '用户名或密码错误' == msg ? '手机号或密码错误' : msg
util.showMsg(true, 'err-txt', msg)
}
}
},
})
}

代码并不长,一个很简单的 post 登录,这里我会一一进行分析

var phone = $('#phone').val().trim()
var pwd = $('#pwd').val()
var fid = $('#fid').val()
var refer = $('#refer').val()
if (util.isEmpty(phone)) {
util.showMsg(true, 'phoneMsg', '请输入手机号', true)
return
}
if (util.isEmpty(pwd)) {
util.showMsg(true, 'pwdMsg', '请输入密码', true)
return
}
var t = $('#t').val()
if (t == 'true') {
pwd = $.base64.btoa(pwd, 'UTF-8')
}

在分割符的前一部分,获取我们表单中的手机号(phone),密码(pwd),学校 id(fid),以及不重要的 refer,同时判断手机号,密码是否为空,并给出相应提示,同时这里的 pwd 还进行了 base64.btoa,也就是 Base64 编码处理过。这里我就模拟一下这些数据

phone = '15212345678'
pwd = 'a123456'
pwd = 'YTEyMzQ1Ng==' // Base64编码后的结果
fid = '12345' // 也可以不指定学校 填-1
refer = 'http://passport2.chaoxing.com'

然后再看剩余的一部分,主要就关注这些:

$.ajax({
url: '/fanyalogin',
type: 'post',
dataType: 'json',
data: {
fid: fid,
uname: phone,
password: pwd,
refer: refer,
t: t,
},
success: function (data) {
//....
}
}

也正是因为这几行代码,将我们的数据发送给了学习通的服务端,并将数据返回给我们,这里我抓个数据包看看数据是怎么样的

POST /fanyalogin HTTP/1.1
Connection: Keep-Alive
Content-Type: application/x-www-form-urlencoded; Charset=UTF-8
Accept: */*
Referer: https://passport2.chaoxing.com/login?&newversion=true
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36
Origin: https://passport2.chaoxing.com
x-requested-with: XMLHttpRequest
Host: passport2.chaoxing.com
Content-Length: 94

fid=12345&uname=15212345678&password=YTEyMzQ1Ng==&refer=http://passport2.chaoxing.com&t=true

认真看上面最后一行,有没有发现这些数据不就是我们刚刚上面模拟的数据。再来看返回数据

HTTP/1.1 200 OK
Server: Tengine
Date: Fri, 25 Sep 2020 11:50:30 GMT
Content-Type: text/html;charset=utf-8
Connection: keep-alive
Set-Cookie: JSESSIONID=625EBEBB8C9A307910975B8A6306EE13; Path=/; HttpOnly
Set-Cookie: lv=3; Domain=.chaoxing.com; Expires=Sun, 25-Oct-2020 11:50:30 GMT; Path=/
Set-Cookie: uf=b2d2c93beefa90dcc0dd308bdb4e3ac7c15d612bf3f08318fdb57793f3e0b0e8e06d6354b86e5f8c6733e63a87a57410913b662843f1f4ad6d92e371d7fdf644cb407fe2f4a1b7e3102289339c6dea121471850d8bf7e34cbde8ab62ef4efbfc29d661c57520821b; Domain=.chaoxing.com; Expires=Sun, 25-Oct-2020 11:50:30 GMT; Path=/
Set-Cookie: _d=1601034630583; Domain=.chaoxing.com; Expires=Sun, 25-Oct-2020 11:50:30 GMT; Path=/
Set-Cookie: vc=D117659BD1295E4489AED8ED14E8A8D8; Domain=.chaoxing.com; Expires=Sun, 25-Oct-2020 11:50:30 GMT; Path=/; HttpOnly
Set-Cookie: vc2=5B73047EF8C636D19D282B878FC42D4A; Domain=.chaoxing.com; Expires=Sun, 25-Oct-2020 11:50:30 GMT; Path=/; HttpOnly
Set-Cookie: vc3=Lptknj6gO2EVnnAWOW0A1O0d0RGWzgO1jVDtKiGtxlqX7dH5uAz84KoWDf2Y9v%2Biw2V3RyKd2gNXf%2BMVKt2HKmJzYK1vt%2BBHu%2B%2BXwG3NtJAWvXygxxcRYlSwCt%2BDv0r8JkrhqgJxJQV2VkMVMon8PABIuJdJKudVTQR%2FP6u2pfY%3D2dd5ab18abc4e7b47fdeb363a13a7c64; Domain=.chaoxing.com; Expires=Sun, 25-Oct-2020 11:50:30 GMT; Path=/; HttpOnly
Set-Cookie: xxtenc=0fd26095d768e519a53edd2f4ba4c9e9; Domain=.chaoxing.com; Expires=Sun, 25-Oct-2020 11:50:30 GMT; Path=/
Set-Cookie: DSSTASH_LOG=C_38-UN_9502-US_42736002-T_1601034630584; Domain=.chaoxing.com; Expires=Sun, 25-Oct-2020 11:50:30 GMT; Path=/
Set-Cookie: route=1b37a788fe3a8c39de935217be0d9f7a;Path=/
Content-Length: 56

{"url":"http%3A%2F%2Fi.mooc.chaoxing.com","status":true}

现在只要注意最后一行,这是登录成功的返回结果,那如果密码错误呢,返回的结果如下

{ "msg2": "用户名或密码错误", "status": false }

那么既然简单了明白了一下基本的登录实现原理,能不能模拟一下这样的请求,替换一下其他人的账号密码,然后发送给服务端,当然可以,看看用易语言代码是怎么实现的这样的登录功能。

注意我划线的两个部分,实际上在 POST 请求中,要做的也就是替换一系列的数据,已达到模拟请求,并接受服务端接收的数据。

不过这里我还得小提一句,看到上面的返回响应中,有好几个 Set-Cookie,这里 Cookie 是服务端返回的,并且加密处理了,而正是这个 Cookie,保存了我们的登录凭证,使得下次我们请求的时候会将这些 Cookie 携带上去,这样服务端才能知道哪个请求是谁发送的。这样才能获取到哪个学生的信息,如课程信息,作业信息等等。

凭什么我发送一个请求给服务端,然后就能登录成功,就可以不用借用浏览器,来实现登录账号?或者说本质上,浏览器数据交互也是通过 HTTP 协议,而 HTTP 协议就是这样,至于实现原理,这里我不涉及太多相关的,你只需要知道可以就行,但有时候并不像上面这么简单(请接着看下文例子)。

从上面的例子中,也许你已经能知道,只要发送一个请求的事情,就可以登录,就能获取对应的信息,实际上,你只要知道下面这一句话:

浏览器本质的操作,就是向服务器发送请求,而软件所做的就是模拟请求,已达到获取数据。

而操作流程,先抓包获取到发送的数据,然后再模拟数据发送给服务端,就可以达到相对应的操作后文都将以这样的操作为例,以至于实现原理,就涉及到相关专业的知识了,同时模拟请求,能绕过浏览器自身的限制(JS 的限制).

获取课程列表

这里我就以获取课程列表数据,并通过 DOM 解析,并将其显示在软件上来演示。

现在已经登录成功了,那么我现在就向服务器发送获取课程信息的请求,在这里我封装成一个易语言的函数,如下几个部分:

url = “http://mooc1-1.chaoxing.com/visit/courses/study?isAjax=true&debug=false
http.Open (GET, url)
http.Send ()
response = http.GetResponseTextU2A ()

此时 response 为 html 文件文本,像这样的

<ul class="clearfix">

<li style="position:relative;" class="courseItem curFile">
<input type="hidden" name="courseId" value="215497310" />
<input type="hidden" name="classId" value="34423626" />

<div class="Mcon1img httpsClass" >
<a href='/visit/stucoursemiddle?courseid=215497310&clazzid=34423626&vc=1&cpi=166457817' target="_blank" >
<img src= https://p.ananas.chaoxing.com/star3/270_160c/56d94e43e4b0dfadae7a3437.jpg onerror="nofind(event)" imagedata='https://p.ananas.chaoxing.com/star3/270_160/56d94e43e4b0dfadae7a3437.jpg' width="270" height="169" />
</a>
<a href="javascript:void(0)" class="move" style="right: 0px;" title="移动到">
移动到 </a>
</div>
<div class="Mconright httpsClass">
<h3 class="clearfix" >
<a class="courseName" href='/visit/stucoursemiddle?courseid=215497310&clazzid=34423626&vc=1&cpi=166457817' target="_blank" title="创业基础">创业基础</a>
<i> </i>
</h3>
<p title="王艳茹">王艳茹 </p>
<p title="中国青年政治学院">中国青年政治学院 </p>
<p title="默认班级">默认班级</p>
<p class="">课程时间:2020年11月12日-2021年01月10日</p>
<p class="Mconrightp3" style="display:none;">
</p>
</div>
</li>
<li class="addLi">
<div class="">
<a href=http://www.fanya.chaoxing.com/schoolcourse/zixuan2?fid=11231 target="_top" class="Mdelc2dt" style="height: 202px;padding-top: 76px;" title="添加课程"><span></span></a>
</div>
</li>
</ul>

可以看到课程信息以及相关链接,接下来要做的就是根据 DOM 对象,提取这些课程数据,下为我那时候解析的代码:

image-20210102035545981

根据 CSS 类名 courseItem,获取课程单元块以及课程数,然后通过遍历 courseItemList,同时通过 CSS 选择器选择到对应的 HTML 标签,获取到我们想要的数据,通过一个自定义数据类型(这里非对象),将其存在课程列表数组内,最后将这些数据通过超级列表框设置到页面上。也就是如下图这样

image-20210102035927028

同样的获取章节列表,作业列表,考试列表,甚至是一些评论列表,也都是通过 DOM 解析,获取其数据,存储到数组内,然后根据章节名或者 id 来获取数组成员,已达到指定课程完成任务。

开始刷课(重点)

如果只是获取数据那怎么能够,而刷课才是软件的主要目的,首先要刷课,就必须要指定课程,这里指定课程也就是 列表框中选中即可,此时点击开始刷课便能开始任务,这里来看看刷课的代码

image-20210102040540378

就是判断用户有无登录,有无任务在执行,有无选课,然后将配置写入到配置文件,方便下次打开还是上次配置,同时设置模式,是要完成那一部分任务,最后将按钮设置为禁止,不可点击(所以为啥我不一开始就直接禁止开始刷课为假呢),最后启动一个线程来执行,在主线程执行会导致窗口卡顿等现象。然后下面才是正在的执行逻辑了。

获取章节列表

image-20210102041501299

添加了注释,就懒得在打字一个个说明了,执行逻辑并不难,也就是判断,然后执行,接着要到刷视频和题这个方法,因为最主要还是这个代码在干什么。(后面也会将写上代码注释,方便大家理解)

开始刷视频和题

image-20210102044334946

开始循环访问选择夹,接下来代码有点多,执行流程也就是循环,判断,我简化了很多,认真看

image-20210102050722756

这里我要提一下,上面的 mArg 数据是什么,是一段 JSON 数据文本,长下面这样

{
"attachments": [
{
"headOffset": 663000,
"jobid": "1596706035431101",
"otherInfo": "nodeId_349140314-cpi_159793445",
"isPassed": true,
"property": {
"jobid": "1596706035431101",
"switchwindow": "true",
"size": 443706433,
"fastforward": "true",
"hsize": "423.15 MB",
"module": "insertvideo",
"name": "12.13.mp4",
"mid": "8562913227181596706035139",
"type": ".mp4",
"doublespeed": 1,
"objectid": "902ca19c673c7fa256702b6211c9df07",
"_jobid": "1596706035431101"
},
"mid": "8562913227181596706035139",
"playTime": 663000,
"type": "video",
"aid": 600168224,
"objectId": "902ca19c673c7fa256702b6211c9df07"
}
],
"defaults": {
"fid": "1617",
"ktoken": "ac0308fc1f7a84019740f1bebfd0b733",
"mtEnc": "a70ce1e7dac8d0d2e6082e2a95d002a3",
"isFiled": 0,
"ignoreVideoCtrl": 0,
"reportUrl": "https://mooc1-2.chaoxing.com/multimedia/log/a/159793445",
"chapterCapture": 0,
"userid": "157041903",
"reportTimeInterval": 60,
"initdataUrl": "https://mooc1-2.chaoxing.com/richvideo/initdatawithviewer",
"knowledgeid": 349140314,
"schooldoublespeed": 0,
"qnenc": "b2a727eed024085321062c005680e1ef",
"defenc": "bca5e389669154f0fd1fb0208b2ad655",
"clazzId": 34189060,
"cardid": 311180449,
"imageUrl": "https://p.ananas.chaoxing.com/star3/270_169c/f01bc30632e023f83b3e8879cdeea2c7.jpg",
"lastmodifytime": 1609462354000,
"state": 0,
"courseid": 215403857,
"cpi": 159793445,
"subtitleUrl": "https://mooc1-2.chaoxing.com/richvideo/subtitle"
},
"control": true
}

通过 JSON 解析工具,可以获取到章节下的课件内容信息。比如视频时长,视频通过状态,视频 id,等等

image-20210102074018606

取到我们想要的数据,并存为变量即可,接着才是关键所在,获取到了课件信息,同时判断课件类型为视频,并且视频的通过状态为 false,那么接下来就是要提交视频了。相关代码如下

提交视频

image-20210102052457877

其中这里提交视频就一个请求,也就是这个请求,服务端才知道你视频看了多少,并且将你的观看时长记录到数据库中,最终拼接的 url 比如这样的 https://mooc1-2.chaoxing.com/multimedia/log/a/159793445/66ee5f706ccc9e58ab0ea383a83e665c?clazzId=34189060&playingTime=935&duration=935&clipTime=0_935&objectId=34ad66ae9778a00c6bfde810f12431ac&otherInfo=nodeId_349140316-cpi_159793445&jobid=1595816983931186&userid=257041903&isdrag=4&view=pc&enc=f26c618c0e0af1147fe2f4ce7b5e8f95&rt=0.9&dtype=Video&_t=160954150532

当然这个请求没这么好伪装出来,你在上面这几行就可以看到这些参数的复杂了,并且还有相关的加密,如果你随便发送一个请求,服务器鸟都不会鸟你的。伪装还算简单,照葫芦画瓢就完事了,而加密你就需要了解对应的加密算法和 JS 逆向了。这里如果我改掉其中一个必要的请求,那么将会出现如下界面

<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name=viewport content="initial-scale=1, minimum-scale=1, width=device-width">
<title>403</title>
<style>
.main{height:255px;margin:0 auto;margin-top:15%;font-size:14px;color:#999; width: 350px;}
.font_top{padding-top:45px;display:inline;}
</style>
</head>
<body>
<div class="main">
<p class="font_top">>_< 很抱歉,您没有权限访问这个页面! (403)</p>
<p style="font-size: 12px; color: #ddd;">112.48.28.255<br/>mooc-2166199849-8f1c1</p>
</div>
</body>
</html>

所以,POST 请求最主要的之一,就是拼接参数,去模拟请求,在说到这里的加密 enc,先看看原文本长啥样

[34189060][157041903][1595816983931186][34ad66ae9778a00c6bfde810f12431ac][935000][d_yHJ!$pdA~5][935000][0_935]

将上面这段数据通过MD5加密即可获取enc为
f26c618c0e0af1147fe2f4ce7b5e8f95

通过易语言拼接如下
web参.enc取数据摘要 (到字节集 ([” + web参.classId + “]” + “[” + web参.userid + “]” + “[” + web参.jobid + “]” + “[” + web参.objectId + “]” + “[” + 到文本 (web参.playingTime × 1000) + “]” + “[d_yHJ!$pdA~5]” + “[” + web参.duration + “000]” + “[0_” + web参.duration + “]))

至于我怎么知道加密点的,就涉及到 JS 逆向,就需要看网页内的 JS 文件,这里就不在赘述了。

然后我们每提交一次视频的请求,只要将 playingTime 播放时长,改为视频时长,就能实现秒刷,原理就是通过协议发送请求。但也有问题所在,我提交一次,服务端记录一次,并且间隔为 1 分钟,我 1 分钟内在提交一次视频完成的请求都将不会记录时长,所以这里的秒刷也只是将视频章节内的视频秒刷了,实际上要挂时长,还得每隔一分钟提交一次才行。(学习通是这样的)

顺便抓个提交测验的请求的包,看看模拟请求有多烦躁。

// url (请求地址)
https://mooc1-2.chaoxing.com/work/addStudentWorkNewWeb?_classId=34189060&courseid=215403857&token=5219a574b7571639ea6b9770bbc23da2&totalQuestionNum=cd8c29480d9c16f5a675e8c2c459245c&ua=pc&formType=post&saveStatus=1&pos=841bf6c56a6d8179e801f5743b&rd=0.5328632906746906&value=(285|546)&wid=11020928&_edt=1609545107317285&version=1

// post的data数据(拼接题号,选项,课程id,班级id,测验id等等......)
pyFlag=&courseId=215403857&classId=34189060&api=1&workAnswerId=50407707&totalQuestionNum=cd8c29480d9c16f5a675e8c2c459245c&fullScore=100.0&knowledgeid=349140316&oldSchoolId=&oldWorkId=62df20e345ff464d9bbe4ea021025cc5&jobid=work-62df20e345ff464d9bbe4ea021025cc5&workRelationId=11020928&enc=&enc_work=5219a574b7571639ea6b9770bbc23da2&userId=157041903&answercheck209790637=A&answercheck209790637=B&answer209790637=AB&answertype209790637=1&answer209790638=false&answertype209790638=3&answer209790639=true&answertype209790639=3&answerwqbid=209790637%2C209790638%2C209790639%2C

// 最后提交请求的响应文本(根据status 是否为true来判断是否提交成功,msg为返回结果文本)
{"msg":"success!","stuStatus":4,"backUrl":"","url":"/api/work?courseid=215403857&workId=ca62b882b279427b9d24876daba4e2ba&clazzId=34189060&knowledgeid=349140316&ut=s&type=&submit=true&jobid=work-62df20e345ff464d9bbe4ea021025cc5&enc=061df255135b657c3aef35c5afc711c4&ktoken=c09603936670a079289c0d1488ab0f63","status":true}

想这样的自动完成任务软件(协议),要做的就是抓包(截包),分析数据,模拟数据,然后在通过代码方式生成出来,最终提交给服务器。

通过这样的流程,就能实现自动刷视频,类似的章节测验与考试无非也就是判断,然后执行发送请求。对这个软件而言,就封装了好几个方法了

image-20210102073628331

而这还只是超星提交的操作,软件界面相关的我就不多演示了。总之,整体执行的流程就是像上面那样,软件怎么编写,就看开发人员了。

关于协议与脚本

上面说了一大堆,相信还是有大多数人迷迷糊糊的,正常,不过听不听得懂无所谓,了解即可,这里我需要说一下,协议与脚本的一些区别,实际上,简单比较下也能很明显的感受到两者的区别,甚至可以说,这两者的本质毫不相干。

  • 脚本

需要依托在宿主上(浏览器),不然也无法执行代码(js 或填表),来实现点击提交操作。脚本要做的,也就是将人手动操作的,通过自动化方式来操作。

  • 协议

则是基于 HTTP,只需要发送请求,就可以做到脱机(浏览器),以达到高效执行,而这是脚本做不到的,同时编写难度也是高于脚本。

发送完成视频的请求,就能绕过浏览器内置的拖拽视频进度条,倍数等限制,而浏览器本身也是基于协议来实现的,也就是将这些提交请求的代码(JS)放在浏览器客户端,然后判断执行。而你的所有操作最终都将通过 HTTP 请求来发送,来达到数据交互的目的。

实际上协议能做的远非如此,我这里简单举几个例子吧,例如抢购,机器人,等等,而这些用脚本都是执行不了,要么执行效率贼差。

实际上在一开始写这款软件时(学易语言和 JS 三个月左右),我是知道脚本与协议的区别,并且一开始想使用脚本来写,奈何,那时候的技术,只停留在使用按键精灵或大漠插件,来实现 PC 端操作,而要操作浏览器内置 DOM 元素,则需要网页填表以及一些前端基础,也正是因为不会这些相关的,但又想写个练手项目,于是就选择使用协议去完成,并且最终成功写成。

现在想想,也正应该感谢这款软件的开发经历,对我技术提升以及后续的学习兴趣至关重要。可以说没有这个软件给我带来的成就感,也许就不会有这篇文章了。(貌似有点提早感慨了)

关于风险

这也是很多人可能会担心的,毕竟软件自动执行嘛,我咋知道安全不安全,会不会导致我账号异常等等。不过这实际上要看情况的。

就先这么说,你在 10 秒内,同时请求了 20 条视频,而后端是知道你请求了 20 条视频,因为你获取了后端的资源,然后后端一看,这丫的不对劲啊,手速这么快,有可能吗?后端不会觉得你手速快,而只会觉得,你像上面代码那样来短时间内批量提交视频的。所以就会给记录异常

而且这也要看平台的

  • 学习通

学习通目前提交过快,就会出现验证码,并未有使账号异常冻结等操作。

  • 职教云

视频提交间隔要在 5 秒以上(我目前测试情况下是这样),课件等无限制。

  • 其他平台(没具体了解过,目前手头就写了这两个)

那如果是正常速度提交,尽可能的模拟人为的操作呢,那也未必就没风险,只要他们后端想,修改一下接口,就能知道你操作是否异常了,比如我更改了加密算法,导致了你提交请求中和服务端效验失败,那我就可以认为你是通过外来手段篡改了请求数据,也就非法操作,就认为你不是非人为(非浏览器)操作。

然而这种情况很少,一般来说,网站部署运行了,除非特别大的改动,基本上是不会频繁的更换源码,需要不断测试。基本上也就是看平台了

我一开始只写了刷视频的,后面对接了题库,就开始写刷题的,然后有验证码就开始过验证码,最终还为软件添加一个网络验证,这每一步的过程都是深夜在电脑前,望着别人完全看不懂的代码,想,改,不过我庆幸我学了这些技术,因为它确实让我目前能写很多能用的项目。从这个超星学习通助手还学到的其他技术,下面一一列举

一些相关技术

完美验证系统

这个是用于验证码识别的,我当初为了解决超星的登录与提交题目时出现的验证码,就必须要识别出该验证码,于是我找到了完美验证码系统,我先放一张图

首先说下识别的实现原理:获取到验证码的图片,比如下面这张

ABCWF

那么我先把干扰线去掉,并且二值化处理一下,变成下面这样

image-20200924232042428

然后这时候开始抠图,抠出 A,B,C,W,F 字符,比如抠出 A 字符

image-20200924232357263

接着,做几百张这样的抠图图片,如

demo4

然后交给系统识别就行了,他会比对你做的字模,然后进行图片相似度比对,最终将识别结果返回给你,看似很简单,的确也很简单,但是我扣这些图,扣了一周,最终识别效果也就只有百分之 50 这样,真的吐了。但是没办法,那时候学的浅,哪里还知道深度学习和 ocr 的识别,就这样坚持硬着头皮扣了一周,然后将这些字模全部导出置识别库用于调用。最终整体识别效果,如下图

image-20200924233820001

甚至还看了一个图象识别的教程手写一个类似这样的识别系统,最终效果如下图。

demo5

我要是当时会深度识别和调用 OCR,我用的着这么麻烦吗,这个是真的浪费我太多没用的时间。

自写网络验证

作为软件开发商,肯定不希望自己的辛辛苦苦写的软件,给别人一破解,修改了版权,并借此牟利,于是就不得不对软件进行一个操作,一般来说外面都有专门的对软件进行保护的厂商,但要钱的嘛,与其如此不如自己写一个,虽然防御上面可能没别人好,但至少一些破解小白肯定没那么轻松搞定。

一般的网络验证,你会看到如下界面

image-20200926161606528

你需要注册一个账号使用,如果时间到期了就需要充值卡密使用,但你如果不注册的话,你就无法使用这个软件,能有效的防止破解者,提高破解门槛(该破解都能破解,就看想不想了),这是一个客户端,对应的肯定有服务端

image-20200926161958435

这里只记录了一些用的上的,实际上可以记录更多,只是我懒得再记录了,通过这个网络验证,可以有效的防止软件被篡改,同时也能利用这个来获取用户引流等等,至于网络验证相关代码就不提及了。

实际上我防破解意识很浅,原因也很简单,这个软件都不收费,别人闲着没事破解啥,原本是加了网络验证,但是由于太麻烦,我就懒得加了。

实际上超星这个软件从头到尾就没主动收取用户费用。那时候也是本着写来自用,并未想过去接单,帮别人啥的,对我来说没必要。软件写来不就是分享给更多的用户吗,话说的绝对,并不是所有开发者都有时间和精力去免费维护一个软件,我之所以能免费分享,主要还是我学校正好就是超星学习通的,加上也是我的练手项目,于是不分享白不分享,到时候一些其他相关软件的合作者也能找我(已经在合作了),所以这个软件对我来说有必要收费吗,反正我是没打算过。但后续平台发展了,就不得不停了该软件,没办法,为了平台而着想。

浏览器插件

我在搜索的超星刷课源码的同时也搜索到一个浏览器插件,油猴插件。相信学校是超星的肯定被同学安利过这样的一款插件,在这个插件你能看到很多脚本,其中你一搜就能搜到有关超星学习通和其他的,并且免费使用,也就是这个原因,我就没打算在我自写的超星学习通助手上进行收费。但实际上它们只是脚本,我当初准备写超星刷课的时候一开始是想写网页自动操作脚本的,但是随便一搜就有了相关的,并且别人的还有一些打码收费什么的,于是就放弃用脚本,而是通过 POST 请求(其实就是我那时候压根就不会浏览器的脚本,搜了一些网页填表的没学明白,而 js 那时候只会分析算法,ES6 语法都不会,于是就选择了协议),然后就有了上面的超星学习通助手,在疫情期间深入学习 web 方面就接触到了两个浏览器脚本,一个是 Puppeteer 与 selenium,另一个是 Chrome 插件开发,而这里的油猴插件就是基于 Chrome 插件开发出来的。

但我没学过油猴插件,而是直接学了 Chrome 插件开发,有关 Chrome 插件开发可以看我写过一篇 Chrome 插件开发的文章,这里的话我就放一个网络上开源的插件 超星慕课小工具 毕竟我的调用题库接口还是基于这个插件的。(现转储为我自己的服务器)

总结

作为作者,能看到更多的人使用自己所写的软件,非常欣慰,估计认识我的一部分也是因为这个软件原因,由于要搞平台,所以就要停滞该项目了。

在一开始编写时,都未曾想到能写的出来,并且优化成这样,在经历了这段编写过程,让我感受的编程的强大,也正是如此,让我会去尝试新鲜的技术,去编写新鲜的项目。不过还要说一些相关的问题

说说这软件一些问题

  • 报毒 :易语言的自身的原因
  • 无法做到适配:有的 win7 系统是获取不到课程,别问,问就是易语言。
  • 无法热更新:每次更新都需要在对应的下载链接下载,到现在我还不会热更新,哭了哭了。
  • 题库不全:有概率是搜不到题目的,所以提供了随机选项。

从写这个软件开始,我能感受到易语言带来的便携,但也看到了易语言的不足之处。以至于我曾最喜欢的编程语言,也渐渐的开始放弃。(补: 我已不再从事易语言开发)

不断更新,维护

没有软件一上来就是完美无瑕的,都是经过多次的修改,更新,最终展现给用户,这个也不例外,从我 去年 10 月 10 号开始一直到现在,中间陆陆续续修改了几十遍,从 1.0 版本更新到 4.1.0(最终版,已不再更新)

感慨

因为这款软件,因为易语言让我感觉到编程是多么无敌的感觉,算是我目前为止还能拿的出手的软件之一,真的完全可以说,没有易语言,我也写不出来这样软件,更别说接下来的学习了。成就感与自信心油然而生,随后的学习更是得心应手。

网络上也很少有类似这种文章,道理想必都懂,最后还是要说下

本文仅作为技术交流,希望更多的人利用技术去方便自己,而不是利用技术从事违规行为

请勿利用本文相关技术与软件从事违法行为,否则后果自负!

Loading Comments...