Compare commits

...

7 Commits

2 changed files with 129 additions and 76 deletions
+80 -63
View File
@@ -162,7 +162,14 @@ def build_response_gapfill(task: TaskInfo) -> dict:
"""gapfill: 填空题 — 优先使用expectedResponse中的正确答案"""
expected = task.expected_response.get("contents", {}).get("gapfill", {})
if expected:
return {"gapfill": expected}
result = {}
for gid, gdata in expected.items():
user_input = gdata.get("userInput", "")
# 处理 "friendlier | more friendly" 多答案格式,取第一个
if " | " in user_input:
user_input = user_input.split(" | ")[0].strip()
result[gid] = {"id": gid, "userInput": user_input}
return {"gapfill": result}
# 回退:从task.detail获取gap列表, 填入空字符串
gf = task.task_detail.get("gapfill", {})
@@ -311,23 +318,33 @@ def build_response_categorisation(task: TaskInfo) -> dict:
def build_response_writing_challenge(task: TaskInfo) -> dict:
"""writing-challenge: 写作挑战 — 优先使用expectedResponse,否则提交占位文本"""
"""writing-challenge: 写作挑战 — 优先使用expectedResponse,否则生成足够字数的占位文本"""
expected = task.expected_response.get("contents", {}).get("writingChallenge", {})
if expected:
if expected and expected.get("userInput", "").strip():
return {"writingChallenge": expected}
# 回退:提交一段较长占位文本(确保满足最低字数要求
# 从task_detail获取字数要求
min_words = task.task_detail.get("writingChallenge", {}).get("minWordCount", 20)
# 生成满足最低字数要求的占位文本
paragraph = (
"I think this is a very interesting topic. There are many things "
"to consider when writing about this subject. First of all, it is "
"important to understand the main ideas and think carefully about "
"the best way to express your thoughts. In my opinion, practice "
"is the key to success and everyone should try their best to "
"improve their skills every day. This is why I believe that "
"learning English is very useful for communication with people "
"from different countries and cultures around the world."
)
word_count = len(paragraph.split())
if word_count < min_words:
repeat = (min_words // word_count) + 1
paragraph = " ".join([paragraph] * repeat)
return {
"writingChallenge": {
"userInput": (
"I think this is a good topic. There are many things to consider "
"when writing about this subject. First of all, it is important to "
"understand the main ideas and think carefully about the best way to "
"express your thoughts. In my opinion, practice is the key to success "
"and everyone should try their best to improve their skills every day. "
"This is why I believe that learning English is very useful for "
"communication with people from different countries and cultures."
),
"userInput": paragraph,
}
}
@@ -873,58 +890,62 @@ class EFCourseAutopilot:
self._log(f"下一单元已激活 (status={put_resp.status_code})")
return True
if is_completed and not next_unit_id:
self._log("当前等级所有单元已完成,尝试切换至下一等级")
try:
levels_url = f"{self.base_url}/wl/api/change-level/levels"
levels_resp = self.session.get(
levels_url, params={"locale": self.locale},
headers=headers, timeout=REQUEST_TIMEOUT,
)
levels_resp.raise_for_status()
levels_data = levels_resp.json()
current_level_id = levels_data.get("currentLevelId")
levels = levels_data.get("levels", [])
if not current_level_id or not levels:
self._log("无法获取等级信息,跳过等级切换")
return False
def _save_diagnostic_report(self):
"""当检测到重复课程时,保存诊断报告供开发者分析"""
import datetime
# 找到 currentLevelId 之后的第一个未开始的等级
found_current = False
next_level = None
for level in levels:
lid = level.get("id") or level.get("levelId")
if not found_current:
if lid == current_level_id:
found_current = True
continue
status = level.get("progress", {}).get("status", "")
if status == "not_started":
next_level = level
break
if status == "started":
next_level = level
break
report = {
"timestamp": datetime.datetime.now().isoformat(),
"course_id": self.ctx.course_id,
"node_id": self.ctx.node_id,
"unit_id": self.ctx.unit_id,
"level_id": self.ctx.level_id,
"lesson_id": self.lesson.lesson_id,
"session_id": self.lesson.session_id,
"activities": [],
"results": [],
}
if next_level:
next_level_id = next_level.get("id") or next_level.get("levelId")
self._log(f"切换至下一等级 (levelId={next_level_id})")
study_plan_url = f"{self.base_url}/wl/api/study-plan/study-plan"
put_resp = self.session.put(
study_plan_url,
params={"locale": self.locale},
json={"courseId": self.ctx.course_id, "levelId": next_level_id},
headers=headers, timeout=REQUEST_TIMEOUT,
)
put_resp.raise_for_status()
self._log(f"等级切换成功 (status={put_resp.status_code})")
return True
else:
self._log("未找到可切换的下一等级")
except Exception as e:
self._log(f"等级切换失败: {e}")
for act in self.activities:
act_info = {
"activity_id": act.activity_id,
"tasks": [
{
"task_id": t.task_id,
"task_type": t.task_type,
"task_detail": t.task_detail,
"expected_response": t.expected_response,
}
for t in act.tasks
],
}
report["activities"].append(act_info)
return False
for r in self.results:
report["results"].append({
"task_id": r.task_id,
"task_type": r.task_type,
"success": r.success,
"skipped": r.skipped,
"error": r.error,
"new_version": r.new_version,
})
return False
safe_cid = self.ctx.course_id[:8] if self.ctx.course_id else "unknown"
safe_nid = self.ctx.node_id[:8] if self.ctx.node_id else "unknown"
filename = f"diagnostic_{safe_cid}_{safe_nid}_{int(time.time())}.json"
with open(filename, "w") as f:
json.dump(report, f, ensure_ascii=False, indent=2)
self._log(f"\n 诊断报告已保存至: {filename}")
self._log(f" 请将此文件提供给开发者以优化对新课程的支持")
# --------------------------------------------------------
# 主流程
@@ -969,10 +990,6 @@ class EFCourseAutopilot:
lesson_key = f"{self.ctx.course_id}:{self.ctx.node_id}"
if lesson_key in self._processed_lessons:
self.repeat_detected = True
self._log("\n⚠️ 检测到重复课程!该课程已处理过但未能晋级。")
self._log(" 请手动登录平台检查课程状态,或联系开发者分析。")
self._save_diagnostic_report()
self._log(f" 诊断文件已保存,请提供给开发者")
return False
# 步骤②: 注册课程会话
+43 -7
View File
@@ -11,6 +11,7 @@ token 需从浏览器开发者工具中获取,详见 README.md。
"""
import argparse
import random
import sys
import time
@@ -36,6 +37,19 @@ def read_token(token_file: str) -> str:
sys.exit(1)
QUOTES = [
"我的答案是乱编的,但我的坚持是真的",
"孩子不会了,所以孩子放弃了",
"代码替我上课,良心替我请假",
"EF 没教会我英语,但我教会了 EF 怎么跑路",
"我提交的不是答案,是对教育系统的一封情书",
"这门课过了,但我的英语水平还卡在加载中",
"别人是来学习的,我是来给服务器刷数据的",
"如果努力有用的话,我还要写脚本干嘛",
"我和这门课,总得先疯一个",
]
def main():
parser = argparse.ArgumentParser(
description="EF Course Autopilot — 交互式循环运行",
@@ -99,21 +113,43 @@ token.txt 说明:
verify_ssl=args.verify_ssl,
)
max_retries = 3
for attempt in range(max_retries + 1):
try:
success = autopilot.run()
if autopilot.repeat_detected:
print("\n⚠️ 检测到重复课程,自动循环终止。")
print(" 请手动登录平台检查课程状态,或联系开发者优化。")
print(f" 诊断文件已保存至当前目录。")
except Exception as e:
print(f"\n❌ 课程执行异常: {e}")
if attempt < max_retries:
print(f" {attempt + 1}/{max_retries} 次重试...\n")
autopilot = EFCourseAutopilot(
token=token,
skip_on_fail=True,
verify_ssl=args.verify_ssl,
)
continue
total_fail += 1
break
if autopilot.repeat_detected:
if attempt < max_retries:
print(f"\n⚠️ 检测到重复,第 {attempt + 1}/{max_retries} 次重试...\n")
autopilot = EFCourseAutopilot(
token=token,
skip_on_fail=True,
verify_ssl=args.verify_ssl,
)
continue
print("\n⚠️ 检测到重复课程,自动循环终止。")
print(f"{random.choice(QUOTES)}」 —— 凯特莎")
print(" 请手动完成此课程,完成后重新运行本程序继续后续课程。")
total_fail += 1
break
if success:
total_success += 1
else:
total_fail += 1
except Exception as e:
print(f"\n❌ 课程执行异常: {e}")
total_fail += 1
break
elapsed = time.time() - start_time
print(f"\n{'#' * 60}")