我是如何与在线选修课(水课)作斗争,同时复习前端知识的

本文讲述了作者如何利用前端知识开发js脚本,自动完成有监控的在线选修课。作者分析了课程平台的机制,通过取消视频自动停止、自动答题和自动进入下一节等功能,成功实现了自动化刷课。同时,文章讨论了可能的反制策略和未来的扩展计划,如自动化登录和封装为浏览器插件。
摘要由CSDN通过智能技术生成

太困了未完待续

题记:从我们的新选修课开始

大三,是迷茫的一年,一边稚气未脱,一边就要独当一面,在这一年,我也不得不开始思考自己未来的方向,并为之努力。
但是,就在这个时候,我们忽然多了一门选修课(必选的选修)。要去我们学校与访问全国大学生创新创业实践联盟合作的网站观看一门创业课程课程并完成练习考试。观看情况,练习成绩和考试结果都会计入最终成绩。
打开网页,登录,进入课程,然后,一点点火花,在黑暗中闪过,引燃了地下的枯木,并迅速挥舞蔓延,唤醒了我从小学到初中到高中一直以来被老师家长不停监督的恐惧。
一课程五章,每张分节,每节听的时间和时长最多查10秒,听完一节才能去下一节,每节中间还有测试,作对才能继续听,这都是网课套路,没什么。但过分的是————不能 切 页 面
只要鼠标离开网页显示部分,无论是最小化,切页面,切其它应用,甚至是移动到浏览器上边框和控制台,视频都会立刻停止播放!
我知道你是不想让学生开网页玩游戏来刷课,但是
这!不是监督!
这!简直是!不信任! 逼迫!

不过想到导员平时的谆谆教诲,还是决定 好(LAO)好(ZI)地(BU)学(XIANG)习(KAN)
上图
页面长这样
我难受,所以,我要力所能及地让自己开心

于是:有压迫就有反抗

我决定 开发一段js脚本,来帮助我刷课(如果可以)
应该也可以用Python等编程语言开发一个爬虫变种,但是目前我的发展方向是前端,于是就靠纯前端方案实现。
(PS:听了前两节,内挺还不错,而且最后考试也是讲课的内容,所以我还是会亲自听的)
但是我还是要写js 因为这是我的态度(有志从事开发的男人)

作战方针

对方人多势众,而我一个穷学生没实力没有体力没有时间没有钱,所以我要找到对方的重要目标,绑架!撕票,然后看剩下的作鸟兽散
基于360浏览器(学校建议使用360打开),在开发者工具–>source–>sinppets中保存脚本,然后手动运行载入网页(因为在这个页面的js代码会有浏览器保存)
测试过程中使用的代码直接由控制台输入
首先会记录我的思考过程和代码,并根据需求分析原网站的代码实现,最后会将先前代码整合。之后会添加完善过程中涉及的前端知识,并尝试更多的方案。

1战争打响

1.1 取消自动停止

1.1.1打倒video

暂停也好,播放也好,都是发生在video上的事件,从它入手应该是个好方法
观察原来文档源文档的video
播放器有一个video元素实现,绑定了一个点击函数
先绑架人质

var videoDom = documet.getElementById('video');

控制台输入 LearnCourse.stop;
在这里插入图片描述
没有改写toString()美滋滋,
这里看到点击视频暂停和播放是通过调用video的原生play()和pause()实现的,自动暂停大概也是如此。那,打倒pause()?

videoDom.onpause = ()=>{
   video.play()}; //视频无法暂停了

这里给视频的暂停函数添加了一个回调,会在视频暂停完成后立即执行(立即播放(~ ̄▽ ̄)~ )
当然也可以直接对pause方法下手

videoDom.pause = ()=>{
   }; 	//视频无法暂停了
videoDom.pause = null; 	//不暂停,会报错,不过没有影响

修改pause和onpause有一个运行逻辑上的区别,前者仍然可以通过点击暂停按钮来暂停播放,后者则是一切暂停的方法都无法使用了

代码执行后,随意移出鼠标都不会暂停了,相当顺利,不过缺乏成就感,所以,继续寻找其他方案

1.1.2擒贼擒王

自动暂停虽然被取消了,但是我仍然不知道该功能的具体实现,而且video毕竟只是个组件,错误应该有指挥他的人来承担,所以我准备寻探索一下原程序是如何得知鼠标移出的。
大致猜测一下,可能使用的事件有通过focus相关,mouse相关等,绑定的对象可能是html,body,外层div甚至是透明蒙版,而实现方案也有事件绑定,注册监听器,修改on方法等。这还只是我第一步能想到的,实际上还会有更多的可能性。向上面的那样逐方法的尝试自然不可行,我要直接去找到源代码中的相关部分。

先让pause方法抛出异常

videoDom.pause = ()=>{
   throw new Error('vide should not stop')}; //是pause不是onpause

不修改onpause是因为我想通过控制台输出找到调用pause()的代码位置
鼠标移动到控制台,报错信息输出
控制台输出
点击第二行定位到代码

		//这是网站原代码,调用了jquery的hover方法,底层依靠 mouseenter 和 mouseleave 事件实现
            $("#hostBody").hover(function(){
   
              return false;
            },function(){
   
                if(!cli){
   
                    var video = document.getElementById("video");
                    if(video){
   
                        video.pause();
                    }
                }
            });

这还不好办?覆盖之!

//#hostBody对应body标签
//其实并不是覆盖
 $("#hostBody").hover(function(){
   videoDom.play()},function(){
   videoDom.play()});

这里牵扯到一个监听器链的概念,jquery底层调用的是addEventListener方法,当对同一个Dom对象的同一事件使用多次addEventListener时,并不会相互覆盖,而是形成一个先注册先运行的监听器链,所以这里的逻辑其实并不是覆盖,而是类似上面onPause那样在暂停之后迅速开始播放。(感觉会很耗性能)
当然,也可以从监听器链中删除之前注册的监听器。
于是,我有了两种取消暂停的方案(・ω<) てへぺろ

第一场战役以自由和民主的胜利告终

后来在代码中发现还注册有visibilityChange监听器,不知为何没有触发(兼容?),以后再研究

1.2播放时自动答题

网课最常见的套路,播放中一个对话框忽然弹出,长这样
在这里插入图片描述
老规矩,先抓人质,但是这个文本框是用layui弹出层框架生成的,也就是说,在出现之前,答题框在html文档中是不存在的,又谈何捕获呢?不过跑得了和尚跑不了呢啥,找不到弹出框,就找弹出框的父元素啊,大不了用定时器循环查询,总能找到的。
观察代码
在这里插入图片描述
即使并不了解layui,也不难看出 弹出层由蒙版(layui-layer-shade)和内容区域(layui-anim)两个平行大div组成,选中后控制台输入$0.parentNode找到父节点

$0.parentNode;//>>> <body style=" background-color:#fff;" id="hostBody">...</body>

捕获

var bodyDom = document.getElementById('hostBody');//捕获body节点
var classTestDom$;

当然,我们并不需要真的用计时器跑循环,因为,可直接监听文档变化

//@ callback 找到后的回调函数,用来答题或者做其他的什么
function findClassTask(callback)
{
    
	//这里出现了一个闭包
	return function(e){
   
		classTestDom = $(bodyDom).find('.layui-layer.layui-layer-page.layer-anim');
		if(classTestDom)
		{
   
			var classTestDomtitle =  $(classTestDom).find('.layui-layer-title')[0];//jquery对象是一个隐式数组
			var title = classTestDomtitle.innerHTML;
			//匹配到标题内容就认为这个弹窗是答题弹窗
			if(/随[\s\S]*堂[\s\S]*测[\s\S]*试/.test(title))
			{
   
				callback();
			}
		}
	};
}
bodyDom.addEventListener('DOMNodeInserted',findClassTask(callback),false);

既然已经抓到了答题框,那就可以开始愉快的答题了
//后来发现这种方法不够严谨,因为答题期间可能会有其他的页面变化触发DOMNodeInserted事件,同时查看文档发现Mutation类事件已经不建议使用,由MutationObserver对象代替

//改写后的代码
var classTestDom;       //答题框
function findClassTask(afterFound)
{
    
    //这里出现了一个闭包
    return function(mutations){
   
        // classTestDom = $(bodyDom).find('.layui-layer.layui-layer-page.layer-anim');
          mutations.forEach(function(mutation) {
   
            for (var i = 0; i < mutation.addedNodes.length; i++)
            {
   
                if($(addedNodes[i]).is(.layui-layer.layui-layer-page.layer-anim))
                {
   
                    var title = addedNodes[i].querySelector(.layui-layer-title).innerHTML;
                    if(/随[\s\S]*堂[\s\S]*测[\s\S]*试/.test(title))
                    {
   
                        //是layui弹窗且有对应的title 就认为是答题框
                        classTestDom = addedNodes[i];
                        afterFound()
                    }
                }
            }
        });
    };
}
function afterFound(){
   ...}

var observer = new MutationObserver(findClassTask(afterFound));         //添加回调
observer.observe(bodyDom, options{
    'childList': true,'subtree':false}); //开始监听

此外也可以去插看源代码,消除弹出方法(不考虑后续影响的话),但是本人并不熟悉layui,所以不使用本方案(后面还有其他原因)

1.2.1暴力消除

题目弹出时,控制台输出了“video should not stop” 的字样,视频继续播放,说明之前弹出式视频时暂停只是靠一次简单的pause()实现的。应该也没有什么复杂的回调。
那么,直接删掉对应元素似乎就好了

$(classTestDom).remove();//jquery方法
classTestDom.parentNode.removeChild(classTestDom[0]);    //原生方法

在删除前,其实还有一个隐含的风险(使用服务器验证答案),打开network面板,静等弹出.
发现答题前后没有发生新的网络请求,说明验证完全由客户端进行,remove!(反正服务器不知道发生了什么);
即使这样,也不能保证无恙,因为客户端代码还有可能临时保存答题结果,然后在某一时刻发送给服务器,对前端显示和业务并不造成影响。
况且,我觉得脚本行为应该尽可能的像人,直接屏蔽一个操作是很不像人的行为,应该让脚本能自己选择并提交才对。(这样才能规避网站目前以及以后的反作弊措施)
PS:虽然经过测试直接的删除并没有什么影响

1.2.2暴力答题

这里分为两个部分:暴力和答题
暴力就是穷举,列出所有可能的组合,并且逐个尝试,至死或答对方休,
专业一点说就是多个输出列出所有排列 不想用递归emmmmm

//遍历二维数组排列
//将每个数组的元素都看作是是对应的下标,求下标的排列,然后按下标取对应元素
//这么说方法名有点不合理,不过算了
function toAnswerList(arrayList,isReturnIndex)
{
   
	
	var result = [];			//最终返回的结果集
	var resultIndex = [];	//返回结果的下标集
	var tempResInd = resIndex; //一个临时量
	var answersList = [];	//arrayList的copy
	var resLength = 1;		//排列总数
	var resIndex = [];		//一组下标,每次按这个下标从各数组中取出一个元素组成一种情况
	//计算排列情况的总数
	for(let index=0;index<arrayList.length;index++)
	{
   
		//过滤掉空对象和空数组
		if(arrayList[index]&&arrayList[index].length==0)
		{
   
			index++;
			continue;
		}
		else
		
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值