新增了"智慧树"平台的AI答题功能;
抽取了部分任务共用逻辑至TaskUtil中;
This commit is contained in:
@@ -12,4 +12,7 @@
|
||||
- 视频播放
|
||||
- 文档浏览
|
||||
- 网页浏览
|
||||
- AI答题
|
||||
> 登录需确保智慧树账号绑定QQ号,使用时需确保QQ在线,通过QQ进行自动登录
|
||||
|
||||
> AI答题部分简答题模块目前只遇到且实现了单空题的作答,不清楚是否存在多个输入框的题目
|
||||
|
||||
@@ -172,7 +172,7 @@ public class CxstudyTask {
|
||||
|
||||
TextQuestion textQuestion = new TextQuestion();
|
||||
textQuestion.setTitle(questionTitle);
|
||||
Answer answer = getAnswer(textQuestion);
|
||||
Answer answer = TaskUtil.getAnswer(textQuestion);
|
||||
TextAnswer textAnswer = (TextAnswer) answer;
|
||||
WebElement inputArea = driver.findElement(By.xpath(".//body[@contenteditable='true']"));
|
||||
//输入答案,并检测是否正确输入
|
||||
@@ -207,7 +207,7 @@ public class CxstudyTask {
|
||||
multiChoiceQuestion.setTitle(questionTitle);
|
||||
List<String> choices = getChoices(choiceElements);
|
||||
multiChoiceQuestion.setChoices(choices);
|
||||
Answer answer = getAnswer(multiChoiceQuestion);
|
||||
Answer answer = TaskUtil.getAnswer(multiChoiceQuestion);
|
||||
MultiChoiceAnswer multiChoiceAnswer = (MultiChoiceAnswer) answer;
|
||||
List<String> answers = multiChoiceAnswer.getAnswers();
|
||||
//输入答案,并检测是否正确输入
|
||||
@@ -233,7 +233,7 @@ public class CxstudyTask {
|
||||
singleChoiceQuestion.setTitle(questionTitle);
|
||||
List<String> choices = getChoices(choiceElements);
|
||||
singleChoiceQuestion.setChoices(choices);
|
||||
Answer primaryAnswer = getAnswer(singleChoiceQuestion);
|
||||
Answer primaryAnswer = TaskUtil.getAnswer(singleChoiceQuestion);
|
||||
SingleChoiceAnswer answer = (SingleChoiceAnswer) primaryAnswer;
|
||||
String answerContent = answer.getAnswer();
|
||||
log.info("答案: {}", answerContent);
|
||||
@@ -264,42 +264,6 @@ public class CxstudyTask {
|
||||
return choices;
|
||||
}
|
||||
|
||||
private Answer getAnswer(Question question) throws InterruptedException {
|
||||
Answer answer = null;
|
||||
int tryTimes = 0;
|
||||
while (tryTimes < 3) {
|
||||
try {
|
||||
String questionStr = JSONUtil.toJsonPrettyStr(question);
|
||||
String responseStr = AiUtil.runChat(questionStr);
|
||||
// 使用正则表达式提取Markdown代码块中的JSON内容
|
||||
String jsonPattern = "```[\\s\\S]*?\\n([\\s\\S]*?)\\n```";
|
||||
Pattern pattern = Pattern.compile(jsonPattern);
|
||||
Matcher matcher = pattern.matcher(responseStr);
|
||||
String jsonStr = "";
|
||||
if (matcher.find()) {
|
||||
jsonStr = matcher.group(1).trim();
|
||||
}
|
||||
if (jsonStr.isEmpty()) {
|
||||
throw new AiResponseFormatException("AI 回答格式不匹配: " + jsonStr);
|
||||
}
|
||||
if (question instanceof SingleChoiceQuestion) {
|
||||
answer = JSONUtil.toBean(jsonStr, SingleChoiceAnswer.class);
|
||||
} else if (question instanceof MultiChoiceQuestion) {
|
||||
answer = JSONUtil.toBean(jsonStr, MultiChoiceAnswer.class);
|
||||
} else if (question instanceof TextQuestion) {
|
||||
answer = JSONUtil.toBean(jsonStr, TextAnswer.class);
|
||||
}
|
||||
break;
|
||||
} catch (AiResponseFormatException e) {
|
||||
log.warn(e.getLocalizedMessage());
|
||||
log.warn("3s后重试...");
|
||||
Thread.sleep(3000);
|
||||
tryTimes++;
|
||||
}
|
||||
}
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
private void handleVideoTask(int i) throws InterruptedException {
|
||||
try {
|
||||
|
||||
@@ -3,12 +3,19 @@ package work.slhaf.task;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.openqa.selenium.*;
|
||||
import org.openqa.selenium.NoSuchElementException;
|
||||
import org.openqa.selenium.interactions.Actions;
|
||||
import org.openqa.selenium.support.ui.ExpectedConditions;
|
||||
import org.openqa.selenium.support.ui.WebDriverWait;
|
||||
import work.slhaf.config.Config;
|
||||
import work.slhaf.exception.TargetClassNotFoundException;
|
||||
import work.slhaf.exception.UnknownTaskPointException;
|
||||
import work.slhaf.pojo.answer.MultiChoiceAnswer;
|
||||
import work.slhaf.pojo.answer.SingleChoiceAnswer;
|
||||
import work.slhaf.pojo.answer.TextAnswer;
|
||||
import work.slhaf.pojo.question.MultiChoiceQuestion;
|
||||
import work.slhaf.pojo.question.SingleChoiceQuestion;
|
||||
import work.slhaf.pojo.question.TextQuestion;
|
||||
import work.slhaf.util.TaskUtil;
|
||||
|
||||
import java.time.Duration;
|
||||
@@ -32,32 +39,13 @@ public class ZhsTask {
|
||||
while (true) {
|
||||
try {
|
||||
switchPage();
|
||||
List<WebElement> todoTasks = getTodoTasks();
|
||||
while (!todoTasks.isEmpty()) {
|
||||
todoTasks.getFirst().click();
|
||||
System.out.println();
|
||||
try {
|
||||
wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//div[@class='resources-list']")));
|
||||
} catch (Exception e) {
|
||||
driver.get(driver.getCurrentUrl());
|
||||
}
|
||||
HashMap<WebElement, String> todoTaskPoints = getTodoTaskPoints();
|
||||
log.info("发现{}个任务点", todoTaskPoints.size());
|
||||
AtomicInteger count = new AtomicInteger(0);
|
||||
for (Map.Entry<WebElement, String> entry : todoTaskPoints.entrySet()) {
|
||||
WebElement k = entry.getKey();
|
||||
String v = entry.getValue();
|
||||
count.incrementAndGet();
|
||||
log.info("处理第{}个任务点", count.get());
|
||||
switch (v) {
|
||||
case "icon-box video" -> handleVideoTask(k);
|
||||
case "icon-box baidu", "icon-box bzhan" -> handleWebTask(k);
|
||||
case "icon-box book", "icon-box other", "icon-box img" -> handleDocTask(k);
|
||||
default -> throw new UnknownTaskPointException("未设置处理策略的任务点类型: " + v);
|
||||
}
|
||||
}
|
||||
driver.findElement(By.xpath("//img[@alt='back']")).click();
|
||||
todoTasks = getTodoTasks();
|
||||
List<WebElement> todoLearningTasks = getTodoLearningTasks();
|
||||
handleTodoLearningTasks(todoLearningTasks);
|
||||
if (aiEnable) {
|
||||
List<WebElement> todoTestTasks = getTodoTestTasks();
|
||||
handleTodoTestTasks(todoTestTasks);
|
||||
}else {
|
||||
log.info("AI答题未开启, 不处理测验任务");
|
||||
}
|
||||
log.info("任务完成!");
|
||||
driver.quit();
|
||||
@@ -77,6 +65,172 @@ public class ZhsTask {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTodoTestTasks(List<WebElement> todoTestTasks) throws InterruptedException {
|
||||
log.info("开始处理测验任务...");
|
||||
while (!todoTestTasks.isEmpty()){
|
||||
todoTestTasks.getFirst().click();
|
||||
waitingTaskPageLoading();
|
||||
//跳转答题页面,并等待题目加载
|
||||
driver.findElement(By.xpath("//div[@class='practice-handle ZHIHUISHU_QZMD']")).click();
|
||||
while (true) {
|
||||
WebElement questionContent = wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//div[@class='questionContent']")));
|
||||
Thread.sleep(3000);
|
||||
//读取题目信息并获取答案
|
||||
String questionType = null;
|
||||
while (questionType == null) {
|
||||
try {
|
||||
questionType = questionContent.findElement(By.xpath(".//div[@class='questionTitle']")).getText().split(" ")[1];
|
||||
}catch (ArrayIndexOutOfBoundsException ignored){
|
||||
Thread.sleep(200);
|
||||
}
|
||||
}
|
||||
String questionTitle = questionContent.findElement(By.xpath(".//div[@class='centent-pre']")).getText();
|
||||
switch (questionType) {
|
||||
case "单选题", "判断题" -> {
|
||||
SingleChoiceQuestion singleChoiceQuestion = new SingleChoiceQuestion();
|
||||
singleChoiceQuestion.setTitle(questionTitle);
|
||||
List<String> choices = new ArrayList<>();
|
||||
List<WebElement> choiceElements = questionContent.findElements(By.xpath(".//li[@class='clearfix']"));
|
||||
for (WebElement choiceElement : choiceElements) {
|
||||
choices.add(choiceElement.getText());
|
||||
}
|
||||
singleChoiceQuestion.setChoices(choices);
|
||||
|
||||
handleSingleChoiceQuestion(singleChoiceQuestion, choiceElements);
|
||||
}
|
||||
case "多选题" -> {
|
||||
MultiChoiceQuestion multiChoiceQuestion = new MultiChoiceQuestion();
|
||||
multiChoiceQuestion.setTitle(questionTitle);
|
||||
List<WebElement> choiceElements = questionContent.findElements(By.xpath(".//label[@class='el-checkbox']"));
|
||||
List<String> choices = new ArrayList<>();
|
||||
HashMap<String, WebElement> choiceElementsMap = new HashMap<>();
|
||||
for (WebElement choiceElement : choiceElements) {
|
||||
String choice = choiceElement.getText();
|
||||
choices.add(choiceElement.getText());
|
||||
choiceElementsMap.put(choice.substring(0, 1), choiceElement);
|
||||
}
|
||||
multiChoiceQuestion.setChoices(choices);
|
||||
|
||||
handleMultiChoiceQuestion(multiChoiceQuestion, choiceElementsMap);
|
||||
}
|
||||
default -> {
|
||||
TextQuestion textQuestion = new TextQuestion();
|
||||
textQuestion.setTitle(questionTitle);
|
||||
|
||||
handleTextQuestion(textQuestion);
|
||||
}
|
||||
|
||||
}
|
||||
//下一题或提交
|
||||
WebElement nextButton = driver.findElement(By.xpath("//span[contains(@class,'next-topic')]"));
|
||||
boolean hasNoNext = nextButton.getDomAttribute("class").contains("noNext");
|
||||
if (hasNoNext) {
|
||||
driver.findElement(By.xpath("//span[contains(@class,'reviewDone')]")).click();
|
||||
break;
|
||||
}else {
|
||||
nextButton.click();
|
||||
}
|
||||
}
|
||||
//回到任务列表页面
|
||||
log.info("作答完毕");
|
||||
wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//div[@class='backup-icon']"))).click();
|
||||
wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//img[@alt='back']"))).click();
|
||||
//重新获取测验任务列表
|
||||
todoTestTasks = getTodoTestTasks();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTextQuestion(TextQuestion textQuestion) throws InterruptedException {
|
||||
TextAnswer textAnswer = (TextAnswer) TaskUtil.getAnswer(textQuestion);
|
||||
String answer = textAnswer.getAnswer();
|
||||
driver.findElement(By.xpath("//input[@class='el-input__inner']")).sendKeys(answer);
|
||||
}
|
||||
|
||||
private void handleMultiChoiceQuestion(MultiChoiceQuestion multiChoiceQuestion, HashMap<String,WebElement> choiceElementsMap) throws InterruptedException {
|
||||
MultiChoiceAnswer multiChoiceAnswer = (MultiChoiceAnswer) TaskUtil.getAnswer(multiChoiceQuestion);
|
||||
List<String> answers = multiChoiceAnswer.getAnswers();
|
||||
for (String answer : answers) {
|
||||
WebElement answerElement = choiceElementsMap.get(answer);
|
||||
while (answerElement.getDomAttribute("class").equals("el-checkbox")) {
|
||||
answerElement.click();
|
||||
Thread.sleep(300);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void handleSingleChoiceQuestion(SingleChoiceQuestion singleChoiceQuestion, List<WebElement> choiceElements) throws InterruptedException {
|
||||
SingleChoiceAnswer singleChoiceAnswer = (SingleChoiceAnswer) TaskUtil.getAnswer(singleChoiceQuestion);
|
||||
String answer = singleChoiceAnswer.getAnswer();
|
||||
for (WebElement choiceElement : choiceElements) {
|
||||
if (choiceElement.getText().startsWith(answer)) {
|
||||
String checkedAttribute = choiceElement.findElement(By.xpath(".//i[contains(@class,'iconfont checkIcon fl')]")).getDomAttribute("class");
|
||||
while ("iconfont checkIcon fl".equals(checkedAttribute)) {
|
||||
choiceElement.click();
|
||||
Thread.sleep(300);
|
||||
checkedAttribute = choiceElement.findElement(By.xpath(".//i[contains(@class,'iconfont checkIcon fl')]")).getDomAttribute("class");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTodoLearningTasks(List<WebElement> todoLearningTasks) throws InterruptedException {
|
||||
log.info("开始处理学习任务...");
|
||||
while (!todoLearningTasks.isEmpty()) {
|
||||
todoLearningTasks.getFirst().click();
|
||||
waitingTaskPageLoading();
|
||||
|
||||
HashMap<WebElement, String> todoTaskPoints = getTodoTaskPoints();
|
||||
log.info("发现{}个任务点", todoTaskPoints.size());
|
||||
AtomicInteger count = new AtomicInteger(0);
|
||||
for (Map.Entry<WebElement, String> entry : todoTaskPoints.entrySet()) {
|
||||
WebElement k = entry.getKey();
|
||||
String v = entry.getValue();
|
||||
count.incrementAndGet();
|
||||
log.info("处理第{}个任务点", count.get());
|
||||
switch (v) {
|
||||
case "icon-box video" -> handleVideoTask(k);
|
||||
case "icon-box baidu", "icon-box bzhan" -> handleWebTask(k);
|
||||
case "icon-box book", "icon-box other", "icon-box img" -> handleDocTask(k);
|
||||
default -> throw new UnknownTaskPointException("未设置处理策略的任务点类型: " + v);
|
||||
}
|
||||
}
|
||||
driver.findElement(By.xpath("//img[@alt='back']")).click();
|
||||
todoLearningTasks = getTodoLearningTasks();
|
||||
}
|
||||
}
|
||||
|
||||
private void waitingTaskPageLoading() {
|
||||
while (true) {
|
||||
try {
|
||||
wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//div[@class='resources-list']")));
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
driver.get(driver.getCurrentUrl());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<WebElement> getTodoTestTasks() {
|
||||
log.info("获取测验任务...");
|
||||
//必须通过item-content定位元素, 但页面在默认情况下都是item-content
|
||||
wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//div[@class='item-content']")));
|
||||
List<WebElement> totalTasks = driver.findElements(By.xpath("//div[@class='item-content']"));
|
||||
List<WebElement> todoTasks = new ArrayList<>();
|
||||
for (WebElement task : totalTasks) {
|
||||
try{
|
||||
String progress = task.findElement(By.xpath(".//div[@class='el-progress__text']")).getText();
|
||||
if (progress.equals("0 %")){
|
||||
todoTasks.add(task);
|
||||
}
|
||||
} catch (NoSuchElementException ignored) {
|
||||
}
|
||||
}
|
||||
log.info("获取到{}个测验任务", todoTasks.size());
|
||||
return todoTasks;
|
||||
}
|
||||
|
||||
private void handleWebTask(WebElement task) throws InterruptedException {
|
||||
//记录当前句柄
|
||||
String primaryHandle = driver.getWindowHandle();
|
||||
@@ -158,7 +312,8 @@ public class ZhsTask {
|
||||
return todoTaskPoints;
|
||||
}
|
||||
|
||||
private List<WebElement> getTodoTasks() {
|
||||
private List<WebElement> getTodoLearningTasks() {
|
||||
log.info("获取学习任务...");
|
||||
List<WebElement> totalTasks;
|
||||
List<WebElement> todoTasks = new ArrayList<>();
|
||||
boolean pct = false;
|
||||
@@ -186,7 +341,7 @@ public class ZhsTask {
|
||||
}
|
||||
todoTasks.add(task);
|
||||
}
|
||||
log.info("获取到{}个任务", todoTasks.size());
|
||||
log.info("获取到{}个学习任务", todoTasks.size());
|
||||
return todoTasks;
|
||||
}
|
||||
|
||||
@@ -207,7 +362,7 @@ public class ZhsTask {
|
||||
throw new TargetClassNotFoundException("未找到课程: " + targetClassName);
|
||||
}
|
||||
targetClass.click();
|
||||
System.out.println();
|
||||
|
||||
}
|
||||
|
||||
private void login() {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package work.slhaf.util;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import io.github.bonigarcia.wdm.WebDriverManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.chrome.ChromeDriver;
|
||||
import org.openqa.selenium.chrome.ChromeOptions;
|
||||
@@ -9,7 +11,20 @@ import org.openqa.selenium.edge.EdgeOptions;
|
||||
import org.openqa.selenium.firefox.FirefoxDriver;
|
||||
import org.openqa.selenium.firefox.FirefoxOptions;
|
||||
import work.slhaf.config.Config;
|
||||
import work.slhaf.exception.AiResponseFormatException;
|
||||
import work.slhaf.pojo.answer.Answer;
|
||||
import work.slhaf.pojo.answer.MultiChoiceAnswer;
|
||||
import work.slhaf.pojo.answer.SingleChoiceAnswer;
|
||||
import work.slhaf.pojo.answer.TextAnswer;
|
||||
import work.slhaf.pojo.question.MultiChoiceQuestion;
|
||||
import work.slhaf.pojo.question.Question;
|
||||
import work.slhaf.pojo.question.SingleChoiceQuestion;
|
||||
import work.slhaf.pojo.question.TextQuestion;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Slf4j
|
||||
public class TaskUtil {
|
||||
private TaskUtil() {}
|
||||
|
||||
@@ -30,4 +45,42 @@ public class TaskUtil {
|
||||
default -> throw new IllegalStateException("不支持的浏览器驱动!: " + config.getBrowser());
|
||||
};
|
||||
}
|
||||
|
||||
public static Answer getAnswer(Question question) throws InterruptedException {
|
||||
Answer answer = null;
|
||||
int tryTimes = 0;
|
||||
while (tryTimes < 3) {
|
||||
try {
|
||||
String questionStr = JSONUtil.toJsonPrettyStr(question);
|
||||
String responseStr = AiUtil.runChat(questionStr);
|
||||
// 使用正则表达式提取Markdown代码块中的JSON内容
|
||||
String jsonPattern = "```[\\s\\S]*?\\n([\\s\\S]*?)\\n```";
|
||||
Pattern pattern = Pattern.compile(jsonPattern);
|
||||
Matcher matcher = pattern.matcher(responseStr);
|
||||
String jsonStr = "";
|
||||
if (matcher.find()) {
|
||||
jsonStr = matcher.group(1).trim();
|
||||
}
|
||||
if (jsonStr.isEmpty()) {
|
||||
throw new AiResponseFormatException("AI 回答格式不匹配: " + jsonStr);
|
||||
}
|
||||
if (question instanceof SingleChoiceQuestion) {
|
||||
answer = JSONUtil.toBean(jsonStr, SingleChoiceAnswer.class);
|
||||
} else if (question instanceof MultiChoiceQuestion) {
|
||||
answer = JSONUtil.toBean(jsonStr, MultiChoiceAnswer.class);
|
||||
} else if (question instanceof TextQuestion) {
|
||||
answer = JSONUtil.toBean(jsonStr, TextAnswer.class);
|
||||
}
|
||||
break;
|
||||
} catch (AiResponseFormatException e) {
|
||||
log.warn(e.getLocalizedMessage());
|
||||
log.warn("3s后重试...");
|
||||
Thread.sleep(3000);
|
||||
tryTimes++;
|
||||
}
|
||||
}
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user