feat(gdgxk): 课程卡片显示培训年度 / 学时 / 完成状态

- list_courses 解析 showCourse 卡片里的「培训年度」「课程学时」「未完成/已完成」,
  返回 section / info / status_label,前端拼成「2026年公需课 · 未完成 · 30 学时」
- 修正完成判定:以平台状态为准(需所有教学活动 100% + 试卷考核 ≥60),
  不再用视频秒数够不够来推断,避免把未完成的课当已学完跳过
This commit is contained in:
study-tool
2026-10-30 22:33:46 +08:00
parent 31f7020190
commit c4bc97dd70
5 changed files with 42 additions and 14 deletions
+37 -10
View File
@@ -142,17 +142,32 @@ def profile(s):
return m.group(1) if m else ""
def _parse_courses(html):
"""课程列表页:每个 `showCourse('id','名称')` 前面就是这张卡片的信息。"""
out = []
for m in re.finditer(r"showCourse\('([^']+)','([^']*)'\)", html):
block = html[max(0, m.start() - 2500):m.start()]
year = re.search(r"培训年度:\s*<[^>]*>(\d{4})<", block)
hours = re.search(r"课程学时:\s*<[^>]*>(\d+)<", block)
st = re.search(r">\s*(未完成|已完成)\s*<", block)
out.append({
"plan_id": m.group(1),
"project_id": "",
"name": m.group(2).strip(),
"section": ("%s年公需课" % year.group(1)) if year else "",
"status_label": st.group(1) if st else "",
"info": ("%s 学时" % hours.group(1)) if hours else "",
"in_study": True,
"platform": PLATFORM_ID,
})
return out
def list_courses(s):
html = s.get(X + "/uc/store/courseRegister", timeout=30).text
out, seen = [], set()
for cid, name in re.findall(r"showCourse\('([^']+)','([^']*)'\)", html):
if cid in seen:
continue
seen.add(cid)
out.append({"plan_id": cid, "project_id": "", "name": name.strip(),
"in_study": True, "platform": PLATFORM_ID})
out = _parse_courses(html)
if not out:
if "loginBtn" in html or "showLogin" in html:
if "logout" not in html:
raise _expired("学习平台会话已失效,请重新登录")
return []
return out
@@ -217,14 +232,21 @@ def discover(s, plan_id, project_id, name=""):
cw["sid"] = sid
sign = _sign(s, {"aid": cw["aid"], "id": cw["id"], "sid": sid}, cw["uid"])
cur = _view_time(s, cw, cw["uid"], sign)
done = cw["total"] > 0 and cur >= cw["total"]
# 完成与否以平台状态为准:要「所有教学活动 100% + 试卷考核 ≥60」,视频秒数够不代表完成
status = ""
for c in _parse_courses(s.get(X + "/uc/store/courseRegister", timeout=30).text):
if c["plan_id"] == cid:
status = c["status_label"]
break
done = status == "已完成"
return {
"user": profile(s),
"course": name or cw["title"],
"progress": "%d/%d 秒" % (cur, cw["total"]),
"progress": "%d/%d 秒%s" % (cur, cw["total"], "(课程已完成)" if done else ""),
"info": cw["video"],
"courseware": [{"id": cw["id"], "title": cw["title"], "href": page,
"total": cw["total"], "viewTime": cur, "done": done,
"status": status,
"aid": cw["aid"], "uid": cw["uid"]}],
"feedback": [], "test": [],
"platform": PLATFORM_ID, "generation": "gxk",
@@ -253,6 +275,11 @@ def study(key, s, d, **opts):
continue
runtime.log(key, "开始学习:%s(总长 %d 秒,已看 %d 秒)"
% (cw["title"], cw["total"], cw["viewTime"]))
if cw["total"] and cw["viewTime"] >= cw["total"]:
runtime.log(key, "视频已播完,但课程仍显示「%s」:还差试卷考核或其他教学活动"
% (cw.get("status") or "未完成"), level="warn")
runtime.set_state(key, done=i)
continue
_learn(key, s, cw, speed, beat)
runtime.set_state(key, done=i)
runtime.set_state(key, status="done", done=total)