318 lines
10 KiB
Go
318 lines
10 KiB
Go
package xuexitong
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"likedream-goframe/internal/application/xuexitong/entity"
|
|
"likedream-goframe/internal/framework/logger"
|
|
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// AutoStudyByCourseID 通过课程ID自动刷课(定时任务主入口)
|
|
func (c *XuexitongCourse) AutoStudyByCourseID(ctx context.Context, courseID string) error {
|
|
return c.AutoStudyByCourseIDWithOptions(ctx, courseID, nil)
|
|
}
|
|
|
|
// AutoStudyByCourseIDWithOptions 通过课程ID自动刷课(带选项)
|
|
func (c *XuexitongCourse) AutoStudyByCourseIDWithOptions(ctx context.Context, courseID string, opts *AutoStudyOptions) error {
|
|
logger.Logger.Info(fmt.Sprintf("开始刷课,课程ID: %s", courseID))
|
|
|
|
courses := c.PullCourseList(ctx)
|
|
if len(courses) == 0 {
|
|
return fmt.Errorf("没有找到任何课程")
|
|
}
|
|
|
|
var targetCourse *entity.XueXiTCourse
|
|
for _, course := range courses {
|
|
if course.CourseID == courseID {
|
|
targetCourse = &course
|
|
break
|
|
}
|
|
}
|
|
|
|
if targetCourse == nil {
|
|
return fmt.Errorf("未找到课程ID为 %s 的课程", courseID)
|
|
}
|
|
|
|
if !targetCourse.IsStart {
|
|
return fmt.Errorf("课程 %s 未开课", targetCourse.CourseName)
|
|
}
|
|
|
|
if targetCourse.State != 0 {
|
|
return fmt.Errorf("课程 %s 已结束", targetCourse.CourseName)
|
|
}
|
|
|
|
if opts != nil {
|
|
return c.AutoStudyCourseWithOptions(ctx, *targetCourse, opts)
|
|
}
|
|
return c.AutoStudyCourse(ctx, *targetCourse)
|
|
}
|
|
|
|
// AutoStudyAllCourses 自动学习所有课程
|
|
func (c *XuexitongCourse) AutoStudyAllCourses(ctx context.Context) error {
|
|
logger.Logger.Info("开始自动刷所有课程任务")
|
|
|
|
courses := c.PullCourseList(ctx)
|
|
if len(courses) == 0 {
|
|
logger.Logger.Warn("没有找到任何课程")
|
|
return nil
|
|
}
|
|
|
|
logger.Logger.Info(fmt.Sprintf("找到 %d 个课程", len(courses)))
|
|
|
|
for i, course := range courses {
|
|
logger.Logger.Info(fmt.Sprintf("开始刷课 [%d/%d]: %s", i+1, len(courses), course.CourseName))
|
|
|
|
if !course.IsStart {
|
|
logger.Logger.Info(fmt.Sprintf("课程 %s 未开课,跳过", course.CourseName))
|
|
continue
|
|
}
|
|
|
|
if course.State != 0 {
|
|
logger.Logger.Info(fmt.Sprintf("课程 %s 已结束,跳过", course.CourseName))
|
|
continue
|
|
}
|
|
|
|
err := c.AutoStudyCourse(ctx, course)
|
|
if err != nil {
|
|
logger.Logger.Error(fmt.Sprintf("刷课失败: %s", course.CourseName), zap.Error(err))
|
|
continue
|
|
}
|
|
|
|
logger.Logger.Info(fmt.Sprintf("完成刷课 [%d/%d]: %s", i+1, len(courses), course.CourseName))
|
|
}
|
|
|
|
logger.Logger.Info("所有课程刷课任务完成")
|
|
return nil
|
|
}
|
|
|
|
// AutoStudyCourse 自动学习指定课程的所有章节
|
|
func (c *XuexitongCourse) AutoStudyCourse(ctx context.Context, course entity.XueXiTCourse) error {
|
|
logger.Logger.Info(fmt.Sprintf("开始刷课程: %s", course.CourseName))
|
|
|
|
chapters := c.PullCourseChapterList(ctx, course)
|
|
if len(chapters) == 0 {
|
|
logger.Logger.Warn(fmt.Sprintf("课程 %s 没有章节", course.CourseName))
|
|
return nil
|
|
}
|
|
|
|
_, err := c.FetchChapterPointStatus(ctx, course, chapters)
|
|
if err != nil {
|
|
logger.Logger.Warn("获取章节状态失败", zap.Error(err))
|
|
}
|
|
|
|
for _, chapter := range chapters {
|
|
if err := c.checkTimeRestriction(); err != nil {
|
|
logger.Logger.Info("时间限制,暂停刷课", zap.String("course", course.CourseName))
|
|
return err
|
|
}
|
|
|
|
if chapter.JobCount > 0 || chapter.PointTotal > chapter.PointFinish {
|
|
err := c.AutoStudyChapter(ctx, course, chapter)
|
|
if err != nil {
|
|
if err == ErrTimeRestriction {
|
|
return err
|
|
}
|
|
logger.Logger.Error(fmt.Sprintf("刷章节失败: %s", chapter.Name), zap.Error(err))
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
logger.Logger.Info(fmt.Sprintf("课程完成: %s", course.CourseName))
|
|
return nil
|
|
}
|
|
|
|
// AutoStudyChapter 自动学习指定章节的所有任务点
|
|
func (c *XuexitongCourse) AutoStudyChapter(ctx context.Context, course entity.XueXiTCourse, chapter entity.XueXiTCourseChapter) error {
|
|
if err := c.EnterChapterForwardCall(ctx, course, chapter.ID); err != nil {
|
|
logger.Logger.Warn("EnterChapterForwardCall失败", zap.Error(err))
|
|
}
|
|
|
|
cards, points, err := c.FetchChapterCards(ctx, course, chapter.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("获取章节任务点失败: %w", err)
|
|
}
|
|
|
|
if len(cards) == 0 || len(points) == 0 {
|
|
logger.Logger.Info(fmt.Sprintf("章节 %s 没有任务点", chapter.Name))
|
|
return nil
|
|
}
|
|
|
|
for i := range points {
|
|
if points[i].Type == entity.ChapterPointTypeVideo {
|
|
logger.Logger.Debug("视频任务点参数",
|
|
zap.String("objectID", points[i].ObjectID),
|
|
zap.String("jobID", points[i].JobID),
|
|
zap.String("otherInfo", points[i].OtherInfo),
|
|
zap.String("attDurationEnc", points[i].AttDurationEnc),
|
|
zap.String("videoFaceCaptureEnc", points[i].VideoFaceCaptureEnc),
|
|
zap.Int("FID", points[i].FID))
|
|
}
|
|
}
|
|
|
|
for i := range points {
|
|
if err := c.checkTimeRestriction(); err != nil {
|
|
logger.Logger.Info("时间限制,暂停刷课", zap.String("chapter", chapter.Name))
|
|
return err
|
|
}
|
|
|
|
point := &points[i]
|
|
if point.Type == 0 {
|
|
continue
|
|
}
|
|
|
|
switch point.Type {
|
|
case entity.ChapterPointTypeVideo:
|
|
if err := c.AutoPlayVideoByPoint(ctx, course, point); err != nil {
|
|
logger.Logger.Error("视频刷课失败", zap.Error(err), zap.String("title", point.Title))
|
|
}
|
|
case entity.ChapterPointTypeDocument:
|
|
if err := c.AutoReadDocumentByPoint(ctx, course, point); err != nil {
|
|
logger.Logger.Error("文档刷课失败", zap.Error(err), zap.String("title", point.Title))
|
|
}
|
|
case entity.ChapterPointTypeWork:
|
|
// 作业需要AI配置,这里暂时跳过,使用 AutoStudyChapterWithAI 方法处理
|
|
logger.Logger.Info("作业任务点需要AI配置,跳过", zap.String("title", point.Title))
|
|
case entity.ChapterPointTypeHyperlink:
|
|
if err := c.AutoCompleteHyperlinkByPoint(ctx, course, point); err != nil {
|
|
logger.Logger.Error("外链刷课失败", zap.Error(err), zap.String("title", point.Title))
|
|
}
|
|
case entity.ChapterPointTypeLive:
|
|
if err := c.AutoWatchLiveByPoint(ctx, course, point); err != nil {
|
|
logger.Logger.Error("直播刷课失败", zap.Error(err), zap.String("title", point.Title))
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AutoStudyOptions 自动学习选项
|
|
type AutoStudyOptions struct {
|
|
AIConfig *AIConfig // AI配置(用于作业答题)
|
|
AutoSubmit bool // 是否自动提交作业
|
|
}
|
|
|
|
// AutoStudyCourseWithOptions 带选项的自动学习课程
|
|
func (c *XuexitongCourse) AutoStudyCourseWithOptions(ctx context.Context, course entity.XueXiTCourse, opts *AutoStudyOptions) error {
|
|
logger.Logger.Info(fmt.Sprintf("开始刷课程: %s", course.CourseName))
|
|
|
|
chapters := c.PullCourseChapterList(ctx, course)
|
|
if len(chapters) == 0 {
|
|
logger.Logger.Warn(fmt.Sprintf("课程 %s 没有章节", course.CourseName))
|
|
return nil
|
|
}
|
|
|
|
_, err := c.FetchChapterPointStatus(ctx, course, chapters)
|
|
if err != nil {
|
|
logger.Logger.Warn("获取章节状态失败", zap.Error(err))
|
|
}
|
|
|
|
for _, chapter := range chapters {
|
|
if err := c.checkTimeRestriction(); err != nil {
|
|
logger.Logger.Info("时间限制,暂停刷课", zap.String("course", course.CourseName))
|
|
return err
|
|
}
|
|
|
|
if chapter.JobCount > 0 && chapter.PointTotal > chapter.PointFinish {
|
|
logger.Logger.Info("开始处理章节", zap.String("chapter", chapter.Name))
|
|
err := c.AutoStudyChapterWithOptions(ctx, course, chapter, opts)
|
|
if err != nil {
|
|
if err == ErrTimeRestriction {
|
|
return err
|
|
}
|
|
logger.Logger.Error(fmt.Sprintf("刷章节失败: %s", chapter.Name), zap.Error(err))
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
logger.Logger.Info(fmt.Sprintf("课程完成: %s", course.CourseName))
|
|
return nil
|
|
}
|
|
|
|
// AutoStudyChapterWithOptions 带选项的自动学习章节
|
|
func (c *XuexitongCourse) AutoStudyChapterWithOptions(ctx context.Context, course entity.XueXiTCourse, chapter entity.XueXiTCourseChapter, opts *AutoStudyOptions) error {
|
|
if err := c.EnterChapterForwardCall(ctx, course, chapter.ID); err != nil {
|
|
logger.Logger.Warn("EnterChapterForwardCall失败", zap.Error(err))
|
|
}
|
|
|
|
cards, points, err := c.FetchChapterCards(ctx, course, chapter.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("获取章节任务点失败: %w", err)
|
|
}
|
|
|
|
if len(cards) == 0 || len(points) == 0 {
|
|
logger.Logger.Info(fmt.Sprintf("章节 %s 没有任务点", chapter.Name))
|
|
return nil
|
|
}
|
|
|
|
logger.Logger.Info("开始处理章节任务点",
|
|
zap.String("chapter", chapter.Name),
|
|
zap.Int("pointCount", len(points)))
|
|
|
|
for i := range points {
|
|
if err := c.checkTimeRestriction(); err != nil {
|
|
logger.Logger.Info("时间限制,暂停刷课", zap.String("chapter", chapter.Name))
|
|
return err
|
|
}
|
|
|
|
point := &points[i]
|
|
if point.IsPassed {
|
|
logger.Logger.Debug("任务点已通过,跳过", zap.String("title", point.Title))
|
|
continue
|
|
}
|
|
if point.Type == 0 {
|
|
logger.Logger.Debug("跳过类型为0的任务点", zap.Int("index", i))
|
|
continue
|
|
}
|
|
|
|
logger.Logger.Info("处理任务点",
|
|
zap.Int("index", i),
|
|
zap.Any("type", point.Type),
|
|
zap.String("title", point.Title),
|
|
zap.String("jobID", point.JobID),
|
|
zap.Bool("isPassed", point.IsPassed))
|
|
|
|
switch point.Type {
|
|
case entity.ChapterPointTypeVideo:
|
|
if err := c.AutoPlayVideoByPoint(ctx, course, point); err != nil {
|
|
logger.Logger.Error("视频刷课失败", zap.Error(err), zap.String("title", point.Title))
|
|
}
|
|
case entity.ChapterPointTypeDocument:
|
|
if err := c.AutoReadDocumentByPoint(ctx, course, point); err != nil {
|
|
logger.Logger.Error("文档刷课失败", zap.Error(err), zap.String("title", point.Title))
|
|
}
|
|
case entity.ChapterPointTypeWork:
|
|
logger.Logger.Info("发现作业任务点",
|
|
zap.String("title", point.Title),
|
|
zap.String("workID", point.WorkID),
|
|
zap.String("jobID", point.JobID),
|
|
zap.Int("cardIndex", point.CardIndex),
|
|
zap.Int("knowledgeID", point.KnowledgeID),
|
|
zap.String("cpi", point.Cpi),
|
|
zap.Bool("hasAIConfig", opts != nil && opts.AIConfig != nil))
|
|
|
|
if opts != nil && opts.AIConfig != nil {
|
|
if err := c.AutoCompleteWorkByPoint(ctx, course, point, opts.AIConfig, opts.AutoSubmit); err != nil {
|
|
logger.Logger.Error("作业刷课失败", zap.Error(err), zap.String("title", point.Title))
|
|
}
|
|
} else {
|
|
logger.Logger.Info("作业任务点需要AI配置,跳过", zap.String("title", point.Title))
|
|
}
|
|
case entity.ChapterPointTypeHyperlink:
|
|
if err := c.AutoCompleteHyperlinkByPoint(ctx, course, point); err != nil {
|
|
logger.Logger.Error("外链刷课失败", zap.Error(err), zap.String("title", point.Title))
|
|
}
|
|
case entity.ChapterPointTypeLive:
|
|
if err := c.AutoWatchLiveByPoint(ctx, course, point); err != nil {
|
|
logger.Logger.Error("直播刷课失败", zap.Error(err), zap.String("title", point.Title))
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|