本文由 @okfang616与@kkkkkba 共创
0. 解决方案
首先,打开蓝桥云课官网(需要先登录
在学习一栏中选择题库,然后选择任意一个题目。
这里以 2. 超级余数 为例,按 F12 打开开发者工具,并切到“console”(控制台)选项卡,如下图所示:
紧接着复制以下代码,到控制台,然后回车执行。
setInterval(() => {
sessionStorage.setItem("oj_learned", "1");
console.log("oj_learned set ok!")
}, 1000)
如下图:
然后你这个浏览器选项卡页面就可以一直挂着了,此为最省事也是成功率最高的方法(和他记录学习时长的原理有关,感兴趣可以继续往下看)
1. 起因
时间回溯到 2024年2月17日 19:22,@kkkkkba 写出了第一版代码:
// 获取具有类名为 'view-line' 的元素
var element = document.querySelector('.view-line');
// 添加20个换行符
for (var i = 0; i < 20; i++) {
element.innerText += "\n";
}
// 保存当前状态
var Text = element.innerText;
// 定义计时器开始时间
var startTime = new Date();
// 初始化有效学习时间
var effectiveStudyTime = 0;
// 启动计时器
var timer = setInterval(function() {
// 获取当前时间
var currentTime = new Date();
// 计算经过的时间(以秒为单位)
var elapsedTime = Math.floor((currentTime - startTime) / 1000);
// 格式化时间为 HH:mm:ss
var hours = Math.floor(elapsedTime / 3600);
var minutes = Math.floor((elapsedTime % 3600) / 60);
var seconds = elapsedTime % 60;
var formattedTime = hours.toString().padStart(2, '0') + ':' +
minutes.toString().padStart(2, '0') + ':' +
seconds.toString().padStart(2, '0');
// 计算有效学习时间
if (elapsedTime % 180 === 0) { // 每3分钟更新一次有效学习时间
effectiveStudyTime++;
}
// 向元素内容中添加当前所计时间并换行
element.innerText = Text + "\n当前计时器:" + formattedTime + "\n有效学习时间:" + effectiveStudyTime * 3 + "分钟";
}, 1000); // 每秒执行一次
kb 的思路很清晰:找到蓝桥云课代码编辑器的文本域DOM,定时改变其中的内容,模拟用户修改代码(假装学习
然而经过我和kb本人的实测,该方法无效。
如果是一年前的我,这时候就要上 selenium
了 🤣,成功率可以说是很高,但是相对来说不太好普及(由于chrome和webdriver的版本问题。会很麻烦…
所以我们把方向调转为其记录时间的原理
2. 侦察兵已上线
既然没有办法通过第一版代码的方案来实现刷分,说明蓝桥云课的防范工作也很足。
我们来一波守株待兔,打开“NetWork”选项卡,把“保留日志”钩上,然后监控 Fetch/XHR
和WS
两边的动静,当然同时也要一直敲击键盘。
大约3分钟后,第一个请求出现,6分钟后,第二个请求出现。至此,我们基本可以断定蓝桥云课是靠接口 /api/v2/problems/${problemId}/record/
来实现的。
如果手动去调用这个接口,会不会是破局的关键点?
3. 断点一打 程序白写
我们点开 “source” (源代码) 选项卡,在 XHR/Fetch断点
里监控所有url包含 “record” 的请求
同样我们在假装学习了3分钟后,成功catch到了。即便代码被混淆过,我们通过调用堆栈依次向上分析,最后还是看到了核心逻辑(下图
function Pl(e, t) {
var n = Object(Or.w)();
sessionStorage.setItem("oj_learned", "0"),
Object(Or.i)((function() {
Al && clearInterval(Al),
Al = setInterval((function() {
if ("1" === sessionStorage.getItem("oj_learned")) {
var r = e.$axios
, o = e.$urls;
r.post(o.problems.reportLearningTime(t), {
page_ident: Object(bn.a)()
}).then((function() {
n.dispatch("auth2/getStudyMinutes"),
sessionStorage.setItem("oj_learned", "0")
}
))
}
}
), 18e4)
}
)),
Object(Or.j)((function() {
clearInterval(Al)
}
))
}
相信懂 js 的小伙伴已经看出来了,setInterval
一出,就是一个定时器,每隔 18e4 ms
(即3分钟)运行一次。每次执行的代码内容就是去判断sessionStorage
里oj_learned
的值是否为1,是的话就去“reportLearningTime”。report完毕后再将oj_learned
设回0。
sessionStorage是HTML5中引入的一种Web Storage API,用于在客户端(浏览器)中保存临时的会话数据。它类似于cookie,但比cookie更强大和安全。
sessionStorage提供了一个简单的键值对存储系统,可以在浏览器的当前会话(即打开的标签页或浏览器窗口)中存储数据。与cookie不同的是,sessionStorage中存储的数据只在同一窗口或标签页中共享,不会被其他窗口或标签页访问到。
我们无从得知 什么条件下才能在原本的逻辑下 让 oj_learned=1
,但是这已经不重要了。sessionStorage 的控制权我们也有,我们只要能保证这个定时器每次执行的时候,oj_learned
的值恒为1即可破局。
从而得到了文章开始的代码:
setInterval(() => {
sessionStorage.setItem("oj_learned", "1");
console.log("oj_learned set ok!")
}, 1000)
4. 摆脱浏览器,拥抱脚本
如果你肯折腾的话,我们也可以把这个过程用Python脚本实现,然后你可以把脚本挂在服务器上运行。
4.1 拿到你的Cookie
我们再次回到案发现场,在控制台内输入以下代码并回车
document.cookie
把红圈圈住的部分存一下,4.2会用到。
4.2 执行
首先安装 requests库
pip install requests
然后复制以下代码:
import time
import requests
import json
cookie = '这里放4.1里你拿到的字符串'
while True:
url = "https://www.lanqiao.cn/api/v2/problems/8179/record/"
payload = json.dumps({
"page_ident": uuid.uuid4().hex
})
headers = {
'DNT': '1',
'Cookie': cookie,
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36',
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
time.sleep(60 * 3 + 5)
执行即可。
4.3 kkkkkb 版代码
import configparser
import http.client
import json
import time
import uuid
import re
# 定义倒计时函数
def countdown(seconds):
for i in range(seconds, 0, -1):
print(f"重新请求倒计时 {i} 秒", end="\r")
time.sleep(1)
cookie = '这里放4.1里你拿到的字符串'
problems = "8179"
print(f"cookie: {cookie}")
print(f"problems: {problems}")
# 无限循环发送 POST 请求
while True:
conn = http.client.HTTPSConnection("www.lanqiao.cn")
payload = json.dumps({
"page_ident": uuid.uuid4().hex
})
headers = {
'DNT': '1',
'Cookie': cookie,
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36',
'Content-Type': 'application/json'
}
# 发送 POST 请求
conn.request("POST", f"/api/v2/problems/{problems}/record/", payload, headers)
res = conn.getresponse()
data = res.read()
response_json = json.loads(data.decode("utf-8"))
print(response_json)
# 如果请求失败且返回了重试时间
if res.status != 200 and response_json.get("code") == "limit_exceed":
# 提取重试秒数
retry_seconds = int(re.search(r"(\d+)秒", response_json.get("message")).group(1))
countdown(retry_seconds) # 进行倒计时
elif res.status == 200:
countdown(180)
# '''
# setInterval(() => {
# sessionStorage.setItem("oj_learned", "1");
# console.log("done");
# }, 6000)
# '''