0x01 前言
内容比较水很简单,这里主要记录一下后面可能还用得到。
接到通知要完成课程学习任务,占一定比例考试成绩,时长需要满差不多30个小时,头都大了。挨个看不知道要到啥时候,全部点开放到后台播放占用资源不说,且会出现加载不动的现象,效果很差。扫一眼看看有什么好办法吧。
0x02 分析
首先得了解一下,它是如何记录保存课程学习时间的。
进入视频学习界面,播放视频,播放状态每过一分钟都会请求一次leaveCellLog接口,而且在暂停的时候也会触发,猜测主要目的是为了记录用户的学习进度、防止挂机、统计学习时长以及提供实时反馈。在每次请求发送之后学习进度确实会随之更新。
观察请求包中的参数,尝试修改非id参数,并不会影响返回包中的时间计时。
于是尝试回溯id参数,在cellDetail请求包中存在id值,推测这个请求应该就是播放视频的作用,id为对应视频课程标识。尝试之后也确实如此,结合leaveCellLog请求就可以保存观看进度。继续回溯cellId。
在courseDirectory中存在callId的值,回溯courseOpenId。
最后发现在list请求中存在courseOpenId的值。
0x03 脚本编写
import requests
import json
import time
import random
import sys
from concurrent.futures import ThreadPoolExecutor, as_completed
# 全局配置
BASE_URL = "xxx"
PROXIES = {
"http": "http://127.0.0.1:8080",
"https": "http://127.0.0.1:8080"
}
# User-Agent 列表
USER_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Safari/605.1.15",
"Mozilla/5.0 (Windows NT 10.0; WOW64; rv:82.0) Gecko/20100101 Firefox/82.0",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36"
]
def get_random_user_agent():
return random.choice(USER_AGENTS)
def send_request(method, endpoint, jwt_token, data=None, params=None):
url = f"{BASE_URL}{endpoint}"
headers = {
"Host": "xxx",
"Accept": "application/json, text/plain, */*",
"access-token": jwt_token,
"User-Agent": get_random_user_agent(),
"Content-Type": "application/json;charset=UTF-8",
"Origin": "xxx",
"Referer": "xxx",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "zh-CN,zh;q=0.9",
"Connection": "close"
}
response = requests.request(method, url, headers=headers, json=data, params=params)
if response.status_code == 200:
return response.json()
else:
print(f"请求 {endpoint} 失败,状态码: {response.status_code}")
return None
def get_course_open_ids(jwt_token):
# 第一步:发送list请求,获取所有课程的 courseOpenId
data = {
"isPass": 0,
"order": "",
"orderField": "",
"pageNum": 1,
"pageSize": 1000
}
response_data = send_request("POST", "/LearningSpace/list", jwt_token, data)
if response_data and response_data["code"] == "200":
courses = response_data["data"]["list"]
course_open_ids = list(set(course["id"] for course in courses))
print(f"找到 {len(course_open_ids)} 个课程开放ID。")
return course_open_ids
else:
return []
def get_video_ids(course_open_id, jwt_token):
# 第二步:带着courseOpenId请求courseDirectoryProcess获取所有视频的cellId
params = {"courseOpenId": course_open_id}
response_data = send_request("GET", "/studyLearn/courseDirectoryProcess", jwt_token, params=params)
if response_data and response_data["code"] == "200":
video_ids = []
modules = response_data["data"]["moduleList"]
for module in modules:
for topic in module["topics"]:
for cell in topic["cells"]:
if cell["subName"] == "视频":
video_ids.append(cell["id"])
print(f"找到 {len(video_ids)} 个视频ID 对应课程开放ID: {course_open_id}。")
return video_ids
else:
return []
def get_video_detail(cell_id, jwt_token):
# 第三步:带着cellId请求cellDetail接口,播放视频,获取保存进度的id
params = {"cellId": cell_id}
response_data = send_request("GET", "/studyLearn/cellDetail", jwt_token, params=params)
if response_data and response_data["code"] == "200":
cell_log_id = response_data["data"]["cellLogId"]
print(f"找到保存进度的ID: 视频日志ID {cell_log_id} 对应视频ID: {cell_id}。")
return cell_log_id
else:
return None
def save_study_progress(cell_log_id, jwt_token):
print(f"### 发送保存学习进度请求: 视频日志ID {cell_log_id} ###")
data = {
"id": cell_log_id,
"stopSeconds": 0,
"videoEndTime": 0
}
response_data = send_request("POST", "/studyLearn/leaveCellLog", jwt_token, data=data)
if response_data and response_data["code"] == "200":
print(f"成功发送保存学习进度请求: 视频日志ID {cell_log_id}")
else:
print("响应错误:", response_data["message"])
def main(jwt_token):
# 第一步:获取所有课程开放ID
course_open_ids = get_course_open_ids(jwt_token)
if not course_open_ids:
print("未找到课程开放ID。")
return
# 第二步:获取所有视频ID
all_video_ids = []
for course_open_id in course_open_ids:
video_ids = get_video_ids(course_open_id, jwt_token)
if video_ids:
all_video_ids.extend(video_ids)
if not all_video_ids:
print("未找到视频ID。")
return
# 第三步:并发获取所有视频保存进度的ID
all_video_log_ids = []
with ThreadPoolExecutor(max_workers=6) as executor:
futures = [executor.submit(get_video_detail, video_id, jwt_token) for video_id in all_video_ids]
for future in as_completed(futures):
video_log_id = future.result()
if video_log_id:
all_video_log_ids.append(video_log_id)
if not all_video_log_ids:
print("未找到视频日志ID。")
return
# 第四步:并发处理所有视频日志ID
start_time = time.time()
duration = random.randint(17 * 60, 25 * 60)
end_time = start_time + duration
print(f"### 开始处理 {len(all_video_log_ids)} 个视频日志ID 的保存学习进度请求 ###")
while time.time() < end_time:
with ThreadPoolExecutor(max_workers=6) as executor:
futures = [executor.submit(save_study_progress, video_log_id, jwt_token) for video_log_id in all_video_log_ids]
for future in as_completed(futures):
future.result()
print(f"### 当前时间: {time.strftime('%Y-%m-%d %H:%M:%S')},休眠 60 秒,继续发送请求 ###")
time.sleep(60)
if __name__ == "__main__":
if len(sys.argv) != 2:
print("用法: python script.py <jwt_token>")
sys.exit(1)
jwt_token = sys.argv[1]
main(jwt_token)
运行脚本: python script.py <jwt_token>
等个十几分钟的就满了。
0x03 小密圈
最后送你一张优惠券,欢迎加入小密圈,好朋友。
原文始发于微信公众号(小黑说安全):记某平台课程学习快速通关
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论