├── scripts ├── .gitignore ├── 结束.bat ├── 运行.bat └── 后台运行.bat ├── .gitignore ├── docs ├── preview_1.gif └── preview_2.png ├── main.go ├── pkg ├── yinghua │ ├── types │ │ ├── captcha.go │ │ ├── nodevideo.go │ │ ├── login.go │ │ ├── chapters.go │ │ └── courses.go │ └── yinghua.go ├── config │ └── const.go ├── task │ └── task.go └── util │ └── util.go ├── sample.config.json ├── bootstrap ├── config.go ├── log.go ├── run.go └── web.go ├── go.mod ├── view └── index.html ├── README.md ├── .github └── workflows │ ├── build.yml │ └── release.yml └── go.sum /scripts/.gitignore: -------------------------------------------------------------------------------- 1 | !*.bat -------------------------------------------------------------------------------- /scripts/结束.bat: -------------------------------------------------------------------------------- 1 | taskkill /f /im aoaostar_mooc.exe -------------------------------------------------------------------------------- /scripts/运行.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | aoaostar_mooc.exe 3 | pause -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | config.json 3 | *.exe 4 | logs 5 | *.bat 6 | aoaostar_mooc -------------------------------------------------------------------------------- /docs/preview_1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/mooc/HEAD/docs/preview_1.gif -------------------------------------------------------------------------------- /docs/preview_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoaostar/mooc/HEAD/docs/preview_2.png -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/aoaostar/mooc/bootstrap" 5 | ) 6 | 7 | func main() { 8 | bootstrap.Run() 9 | } 10 | -------------------------------------------------------------------------------- /scripts/后台运行.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | if "%1" == "h" goto begin 3 | mshta vbscript:createobject("wscript.shell").run("%~nx0 h",0)(window.close)&&exit 4 | :begin 5 | 6 | aoaostar_mooc.exe 7 | -------------------------------------------------------------------------------- /pkg/yinghua/types/captcha.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type Captcha struct { 4 | Status string `json:"status"` 5 | Message string `json:"message"` 6 | Data interface{} `json:"data"` 7 | } 8 | -------------------------------------------------------------------------------- /sample.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "global": { 3 | "server": ":10086", 4 | "limit": 3 5 | }, 6 | "users": [ 7 | { 8 | "base_url": "https://mooc.yinghuaonline.com/", 9 | "school_id": 0, 10 | "username": "username", 11 | "password": "password" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /pkg/config/const.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Config struct { 4 | Global Global `json:"global"` 5 | Users []User `json:"users"` 6 | } 7 | type Global struct { 8 | Server string `json:"server"` 9 | Limit int `json:"limit"` 10 | } 11 | type User struct { 12 | BaseURL string `json:"base_url"` 13 | SchoolID int `json:"school_id"` 14 | Username string `json:"username"` 15 | Password string `json:"password"` 16 | } 17 | 18 | var Conf Config 19 | 20 | const VERSION = "v1.3.2" 21 | -------------------------------------------------------------------------------- /bootstrap/config.go: -------------------------------------------------------------------------------- 1 | package bootstrap 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "github.com/aoaostar/mooc/pkg/config" 7 | "io" 8 | "os" 9 | ) 10 | 11 | func InitConfig() error { 12 | 13 | file, err := os.Open("./config.json") 14 | if err != nil { 15 | return errors.New("读取配置文件失败: " + err.Error()) 16 | } 17 | readAll, err := io.ReadAll(file) 18 | if err != nil { 19 | return err 20 | } 21 | 22 | err = json.Unmarshal(readAll, &config.Conf) 23 | 24 | if err != nil { 25 | return err 26 | } 27 | return nil 28 | 29 | } 30 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aoaostar/mooc 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/go-resty/resty/v2 v2.7.0 7 | github.com/sirupsen/logrus v1.9.0 8 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 9 | ) 10 | 11 | require ( 12 | github.com/BurntSushi/toml v1.2.1 // indirect 13 | github.com/EDDYCJY/fake-useragent v0.2.0 // indirect 14 | github.com/PuerkitoBio/goquery v1.8.0 // indirect 15 | github.com/andybalholm/cascadia v1.3.1 // indirect 16 | github.com/stretchr/testify v1.8.0 // indirect 17 | golang.org/x/net v0.0.0-20221014081412-f15817d10f9b // indirect 18 | golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect 19 | gopkg.in/yaml.v2 v2.4.0 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /bootstrap/log.go: -------------------------------------------------------------------------------- 1 | package bootstrap 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "gopkg.in/natefinch/lumberjack.v2" 6 | "io" 7 | "os" 8 | ) 9 | 10 | func InitLog() { 11 | 12 | logWriter := &lumberjack.Logger{ 13 | Filename: "./logs/aoaostar.log", //日志文件位置 14 | MaxSize: 5, // 单文件最大容量,单位是MB 15 | MaxBackups: 3, // 最大保留过期文件个数 16 | MaxAge: 7, // 保留过期文件的最大时间间隔,单位是天 17 | Compress: false, // 是否需要压缩滚动日志, 使用的 gzip 压缩 18 | LocalTime: true, 19 | } 20 | logrusFormatter := &logrus.TextFormatter{ 21 | ForceColors: true, 22 | FullTimestamp: true, 23 | TimestampFormat: "2006-01-02 15:04:05", 24 | } 25 | logrus.SetFormatter(logrusFormatter) 26 | 27 | logrus.SetOutput(io.MultiWriter(os.Stdout, logWriter)) 28 | 29 | logrus.SetLevel(logrus.InfoLevel) 30 | 31 | //logrus.SetReportCaller(true) 32 | 33 | } 34 | -------------------------------------------------------------------------------- /pkg/yinghua/types/nodevideo.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // NodeVideoResponse GetNodeProgress 4 | type NodeVideoResponse struct { 5 | Code int `json:"_code"` 6 | Status bool `json:"status"` 7 | Msg string `json:"msg"` 8 | Result Result `json:"result"` 9 | } 10 | type NodeVideoStudyTotal struct { 11 | Duration string `json:"duration"` 12 | Progress string `json:"progress"` 13 | State string `json:"state"` 14 | } 15 | type NodeVideoCheat struct { 16 | State int `json:"state"` 17 | Duration int `json:"duration"` 18 | } 19 | type NodeVideoData struct { 20 | VideoID string `json:"videoId"` 21 | VideoMime string `json:"videoMime"` 22 | VideoDuration int `json:"videoDuration"` 23 | VideoToken string `json:"videoToken"` 24 | StudyTotal NodeVideoStudyTotal `json:"study_total"` 25 | Cheat NodeVideoCheat `json:"cheat"` 26 | } 27 | type Result struct { 28 | Data NodeVideoData `json:"data"` 29 | } 30 | -------------------------------------------------------------------------------- /bootstrap/run.go: -------------------------------------------------------------------------------- 1 | package bootstrap 2 | 3 | import ( 4 | "fmt" 5 | "github.com/aoaostar/mooc/pkg/config" 6 | "github.com/aoaostar/mooc/pkg/task" 7 | "github.com/aoaostar/mooc/pkg/util" 8 | "github.com/aoaostar/mooc/pkg/yinghua" 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | func Run() { 13 | 14 | InitLog() 15 | 16 | util.Copyright() 17 | 18 | err := InitConfig() 19 | 20 | if err != nil { 21 | logrus.Fatal(err) 22 | } 23 | 24 | go InitWeb() 25 | 26 | for _, user := range config.Conf.Users { 27 | send(user) 28 | } 29 | task.Start() 30 | 31 | } 32 | func send(user config.User) { 33 | 34 | instance := yinghua.New(user) 35 | 36 | err := instance.Login() 37 | if err != nil { 38 | logrus.Fatal(err) 39 | } 40 | instance.Output(fmt.Sprintf("登录成功")) 41 | 42 | err = instance.GetCourses() 43 | if err != nil { 44 | logrus.Fatal(err) 45 | } 46 | 47 | instance.Output(fmt.Sprintf("获取全部在学课程成功, 共计 %d 门\n", len(instance.Courses))) 48 | 49 | for _, course := range instance.Courses { 50 | task.Tasks = append(task.Tasks, task.Task{ 51 | User: user, 52 | Course: course, 53 | Status: false, 54 | }) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /bootstrap/web.go: -------------------------------------------------------------------------------- 1 | package bootstrap 2 | 3 | import ( 4 | "github.com/aoaostar/mooc/pkg/config" 5 | "github.com/aoaostar/mooc/pkg/util" 6 | "github.com/sirupsen/logrus" 7 | "io" 8 | "net/http" 9 | "os" 10 | "strings" 11 | ) 12 | 13 | func InitWeb() { 14 | http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { 15 | 16 | file, err := os.Open("./view/index.html") 17 | if err != nil { 18 | logrus.Fatal(err.Error()) 19 | } 20 | readAll, err := io.ReadAll(file) 21 | 22 | if err != nil { 23 | logrus.Fatal(err.Error()) 24 | } 25 | _, err = io.WriteString(writer, string(readAll)) 26 | 27 | if err != nil { 28 | logrus.Fatal(err.Error()) 29 | } 30 | 31 | }) 32 | http.HandleFunc("/ajax", func(writer http.ResponseWriter, request *http.Request) { 33 | 34 | text, err := util.ReadText("./logs/aoaostar.log", 0, 100) 35 | if err != nil { 36 | logrus.Error(err) 37 | 38 | } 39 | _, err = io.WriteString(writer, strings.Join(text, "\n")) 40 | if err != nil { 41 | logrus.Error(err) 42 | } 43 | 44 | }) 45 | logrus.Infof("web端启动成功, 请访问 %s 查看服务状态", config.Conf.Global.Server) 46 | err := http.ListenAndServe(config.Conf.Global.Server, nil) 47 | if err != nil { 48 | logrus.Fatal(err.Error()) 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /view/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 傲星网课助手 6 | 7 | 32 | 33 | 34 |
35 | 36 |
37 | 48 | 49 | -------------------------------------------------------------------------------- /pkg/yinghua/types/login.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type LoginResponse struct { 4 | Code int `json:"_code"` 5 | Status bool `json:"status"` 6 | Msg string `json:"msg"` 7 | Result LoginResult `json:"result"` 8 | } 9 | type LoginReset struct { 10 | State int `json:"state"` 11 | Pwd string `json:"pwd"` 12 | } 13 | type LoginData struct { 14 | ID int `json:"id"` 15 | Token string `json:"token"` 16 | Avatar string `json:"avatar"` 17 | Number string `json:"number"` 18 | Name string `json:"name"` 19 | IDCard string `json:"idCard"` 20 | Gender string `json:"gender"` 21 | EntryYear string `json:"entryYear"` 22 | Mobile string `json:"mobile"` 23 | WeChat string `json:"weChat"` 24 | Email string `json:"email"` 25 | Intro string `json:"intro"` 26 | ClassID int `json:"classId"` 27 | ClassName string `json:"className"` 28 | CollegeID int `json:"collegeId"` 29 | CollegeName string `json:"collegeName"` 30 | Province int `json:"province"` 31 | City int `json:"city"` 32 | Region int `json:"region"` 33 | ProvinceName string `json:"provinceName"` 34 | CityName string `json:"cityName"` 35 | RegionName string `json:"regionName"` 36 | Address string `json:"address"` 37 | Point int `json:"point"` 38 | Rank int `json:"rank"` 39 | TipPass int `json:"tipPass"` 40 | Force int `json:"force"` 41 | Reset LoginReset `json:"reset"` 42 | } 43 | type LoginResult struct { 44 | Data LoginData `json:"data"` 45 | } 46 | -------------------------------------------------------------------------------- /pkg/yinghua/types/chapters.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type ChaptersResponse struct { 4 | Code int `json:"_code"` 5 | Status bool `json:"status"` 6 | Msg string `json:"msg"` 7 | Result ChaptersResult `json:"result"` 8 | } 9 | type ChaptersNodeList struct { 10 | ID int `json:"id"` 11 | Name string `json:"name"` 12 | VoteURL string `json:"voteUrl"` 13 | TabVideo bool `json:"tabVideo"` 14 | TabFile bool `json:"tabFile"` 15 | TabVote bool `json:"tabVote"` 16 | TabWork bool `json:"tabWork"` 17 | TabExam bool `json:"tabExam"` 18 | VideoDuration string `json:"videoDuration"` 19 | UnlockTime string `json:"unlockTime"` 20 | NodeLock int `json:"nodeLock"` 21 | UnlockTimeStamp int `json:"unlockTimeStamp"` 22 | VideoState int `json:"videoState"` 23 | Duration string `json:"duration"` 24 | Index string `json:"index"` 25 | Idx int `json:"idx"` 26 | } 27 | type ChaptersList struct { 28 | ID int `json:"id"` 29 | Name string `json:"name"` 30 | NodeList []ChaptersNodeList `json:"nodeList"` 31 | Idx int `json:"idx"` 32 | } 33 | type ChaptersResult struct { 34 | List []ChaptersList `json:"list"` 35 | } 36 | 37 | type StudyNodeResponse struct { 38 | Code int `json:"_code"` 39 | NeedCode bool `json:"need_code"` 40 | Status bool `json:"status"` 41 | Msg string `json:"msg"` 42 | Result StudyNodeResult `json:"result"` 43 | } 44 | type StudyNodeData struct { 45 | StudyID int `json:"studyId"` 46 | } 47 | type StudyNodeResult struct { 48 | Data StudyNodeData `json:"data"` 49 | } 50 | -------------------------------------------------------------------------------- /pkg/task/task.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "fmt" 5 | "github.com/aoaostar/mooc/pkg/config" 6 | "github.com/aoaostar/mooc/pkg/yinghua" 7 | "github.com/aoaostar/mooc/pkg/yinghua/types" 8 | "github.com/sirupsen/logrus" 9 | "math" 10 | "sync" 11 | ) 12 | 13 | type Task struct { 14 | User config.User 15 | Course types.CoursesList 16 | Status bool 17 | } 18 | 19 | var Tasks []Task 20 | 21 | func Start() { 22 | limit := int(math.Min(float64(config.Conf.Global.Limit), float64(len(Tasks)))) 23 | jobs := make(chan Task, limit) 24 | wg := sync.WaitGroup{} 25 | for i := 0; i < limit; i++ { 26 | go func() { 27 | defer wg.Done() 28 | for job := range jobs { 29 | work(job) 30 | } 31 | }() 32 | wg.Add(1) 33 | } 34 | 35 | logrus.Infof("任务系统启动成功, 协程数: %d, 任务数: %d", limit, len(Tasks)) 36 | 37 | for _, task := range Tasks { 38 | jobs <- task 39 | } 40 | close(jobs) 41 | wg.Wait() 42 | logrus.Infof("恭喜您, 所有任务都已全部完成~~~") 43 | } 44 | 45 | func work(task Task) { 46 | instance := yinghua.New(task.User) 47 | err := instance.Login() 48 | if err != nil { 49 | logrus.Fatal(err) 50 | } 51 | 52 | instance.Output("登录成功") 53 | 54 | if task.Course.Progress == 1 { 55 | instance.Output(fmt.Sprintf("当前课程[%s][%d] 进度: %s, 跳过", task.Course.Name, task.Course.ID, task.Course.Progress1)) 56 | return 57 | } 58 | if task.Course.State == 2 { 59 | instance.Output(fmt.Sprintf("当前课程[%s][%d] 已结束, 跳过", task.Course.Name, task.Course.ID)) 60 | return 61 | } 62 | instance.Output(fmt.Sprintf("当前课程[%s][%d] 进度: %s", task.Course.Name, task.Course.ID, task.Course.Progress1)) 63 | err = instance.StudyCourse(task.Course) 64 | if err != nil { 65 | instance.OutputWith(fmt.Sprintf("课程[%s][%d]: %s", task.Course.Name, task.Course.ID, err.Error()), logrus.Errorf) 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 说明 2 | 3 | 英华学堂网课助手客户端版本 4 | 作者: Pluto 5 | 禁止二次售卖, 仅供学习与交流 6 | 代刷慕课的请不要使用, 我讨厌代刷慕课的 7 | 8 | ## 演示 9 | 10 | ![](docs/preview_1.gif) 11 | 12 | ### web端 13 | 14 | ![](docs/preview_2.png) 15 | 16 | ## 使用 17 | ### 教学视频 18 | 19 | 20 | ### 文字教程 21 | * 在`releases`页面下载最新版, 到电脑 22 | + `windows`系统请下载文件名带`windows`的, 别傻乎乎的下载`linux`的 23 | + 直达: 24 | 25 | * 配置好`config.json`, 直接双击运行`运行.bat`即可,窗口不能关闭, 只能最小化 26 | + 关闭了还问我怎么没有了, 程序都没有运行了, 怎么可能还有 27 | * 也可以双击运行`后台运行.bat`让程序在后台运行 ( 没有窗口 ) 28 | * 打开 可以在浏览器查看服务状态 29 | * 如果想要结束后台程序请运行`结束.bat`结束后台程序 30 | * 如果启动一直卡住,没有东西输出,请在系统防火墙添加一下应用名单或者直接关闭防火墙 31 | 32 | ### linux系统 33 | 自己琢磨, 不教 34 | 35 | ### 配置 36 | 37 | > ~~`base_url`使用`mooc.yinghuaonline.com`时`school_id`为必填项~~ 38 | > ~~使用自己学校的平台可以不填,默认为`0` (推荐)~~ 39 | 40 | > `base_url`请填写自己学校的平台域名,不要有路径地址 41 | > `school_id`请填写`0` 42 | > `server`网页端地址, `:10086`=> `127.0.0.1:10086` ( 不懂就不要改 ) 43 | > `limit`协程数, 支持多门课程一起刷, 拉满 ( 填数字就行了, 99也行 ) 可以以最快速度刷完 (推荐拉满) 44 | > JSON在线编辑工具: 45 | 46 | ```json 47 | { 48 | "global": { 49 | "server": ":10086", 50 | "limit": 3 51 | }, 52 | "users": [ 53 | { 54 | "base_url": "https://mooc.yinghuaonline.com", 55 | "school_id": 0, 56 | "username": "username", 57 | "password": "password" 58 | } 59 | ] 60 | } 61 | ``` 62 | #### 举个栗子 63 | 64 | ```json 65 | { 66 | "global": { 67 | "server": ":10086", 68 | "limit": 999999 69 | }, 70 | "users": [ 71 | { 72 | "base_url": "https://mooc.school.com", 73 | "school_id": 0, 74 | "username": "233123321", 75 | "password": "a1234567" 76 | } 77 | ] 78 | } 79 | ``` 80 | 81 | #### 支持多账号 82 | 83 | ```json 84 | { 85 | "global": { 86 | "server": ":10086", 87 | "limit": 3 88 | }, 89 | "users": [ 90 | { 91 | "base_url": "https://mooc.yinghuaonline.com/", 92 | "school_id": 0, 93 | "username": "username1", 94 | "password": "password1" 95 | }, 96 | { 97 | "base_url": "https://mooc.yinghuaonline.com/", 98 | "school_id": 0, 99 | "username": "username2", 100 | "password": "password2" 101 | } 102 | ] 103 | } 104 | ``` -------------------------------------------------------------------------------- /pkg/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "github.com/aoaostar/mooc/pkg/config" 7 | "github.com/sirupsen/logrus" 8 | "os" 9 | "runtime" 10 | "strconv" 11 | "strings" 12 | ) 13 | 14 | func SaveJson(filename, data string) { 15 | 16 | file, _ := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0744) 17 | 18 | _, _ = file.WriteString(data) 19 | } 20 | 21 | func GetGid() (gid uint64) { 22 | b := make([]byte, 64) 23 | b = b[:runtime.Stack(b, false)] 24 | b = bytes.TrimPrefix(b, []byte("goroutine ")) 25 | b = b[:bytes.IndexByte(b, ' ')] 26 | n, err := strconv.ParseUint(string(b), 10, 64) 27 | if err != nil { 28 | panic(err) 29 | } 30 | return n 31 | } 32 | 33 | func ReadText(filename string, line int, limit int) ([]string, error) { 34 | var data []string 35 | file, err := os.Open(filename) 36 | if err != nil { 37 | return []string{}, err 38 | } 39 | 40 | var count = 0 41 | s := bufio.NewScanner(file) 42 | for s.Scan() { 43 | count++ 44 | } 45 | //超出行数 46 | if line > count { 47 | return []string{}, nil 48 | } 49 | _, err = file.Seek(0, 0) 50 | if err != nil { 51 | return nil, err 52 | } 53 | r := bufio.NewScanner(file) 54 | if line == 0 { 55 | if limit == 0 { 56 | line = count 57 | } else { 58 | line = limit 59 | } 60 | } 61 | index := 0 62 | for r.Scan() { 63 | if limit != 0 && len(data) >= limit { 64 | break 65 | } 66 | if index >= count-line { 67 | data = append(data, strings.TrimSpace(r.Text())) 68 | } 69 | index++ 70 | } 71 | return data, nil 72 | } 73 | 74 | func Copyright() { 75 | logrus.Infof(` 76 | +---------------------------------------------------------------------------------------+ 77 | ___ ___ ___ ___ ___ ___ ___ ___ 78 | /\ \ /\ \ /\ \ /\ \ /\ \ /\ \ /\ \ /\ \ 79 | /::\ \ /::\ \ /::\ \ /::\ \ /::\ \ \:\ \ /::\ \ /::\ \ 80 | /::\:\__\ /:/\:\__\ /::\:\__\ /:/\:\__\ /\:\:\__\ /::\__\ /::\:\__\ /::\:\__\ 81 | \/\::/ / \:\/:/ / \/\::/ / \:\/:/ / \:\:\/__/ /:/\/__/ \/\::/ / \;:::/ / 82 | /:/ / \::/ / /:/ / \::/ / \::/ / \/__/ /:/ / |:\/__/ 83 | \/__/ \/__/ \/__/ \/__/ \/__/ \/__/ \|__| 84 | 85 | Github: https://github.com/aoaostar/mooc 86 | Version: %s Runtime: %s/%s Go Version: %s Author: Pluto 87 | +---------------------------------------------------------------------------------------+ 88 | `, config.VERSION, runtime.GOOS, runtime.GOARCH, runtime.Version()) 89 | } 90 | -------------------------------------------------------------------------------- /pkg/yinghua/types/courses.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type CoursesResponse struct { 4 | Code int `json:"_code"` 5 | Status bool `json:"status"` 6 | Msg string `json:"msg"` 7 | Result CoursesResult `json:"result"` 8 | } 9 | type CoursesList struct { 10 | ID int `json:"id"` 11 | Name string `json:"name"` 12 | Mode int `json:"mode"` 13 | CollegeID int `json:"collegeId"` 14 | CategoryID int `json:"categoryId"` 15 | Lecturers string `json:"lecturers"` 16 | StartDate string `json:"startDate"` 17 | EndDate string `json:"endDate"` 18 | Cover string `json:"cover"` 19 | Credit string `json:"credit"` 20 | Intro string `json:"intro"` 21 | Code string `json:"code"` 22 | StuCount string `json:"stuCount"` 23 | Proclamation interface{} `json:"proclamation"` 24 | ClusterID string `json:"clusterId"` 25 | PeriodName string `json:"periodName"` 26 | AddTime string `json:"addTime"` 27 | CreateID string `json:"createId"` 28 | SchoolID string `json:"schoolId"` 29 | CateBid string `json:"cateBid"` 30 | CateMid string `json:"cateMid"` 31 | SignStartTime string `json:"signStartTime"` 32 | SignEndTime string `json:"signEndTime"` 33 | SignScope string `json:"signScope"` 34 | SignClass string `json:"signClass"` 35 | LecturerName string `json:"lecturerName"` 36 | Offline string `json:"offline"` 37 | Mission string `json:"mission"` 38 | SignLimit int `json:"signLimit"` 39 | LineLock string `json:"lineLock"` 40 | AddDate string `json:"addDate"` 41 | TplID string `json:"tplId"` 42 | VideoCount int `json:"videoCount"` 43 | VideoLearned int `json:"videoLearned"` 44 | Sign int `json:"sign"` 45 | CollegeName string `json:"collegeName"` 46 | CategoryName string `json:"categoryName"` 47 | Teachers string `json:"teachers"` 48 | ScoreRuleURL string `json:"scoreRuleUrl"` 49 | SignState int `json:"signState"` 50 | Progress float32 `json:"progress"` 51 | Progress1 string `json:"progress1"` 52 | ResultScore float32 `json:"resultScore"` 53 | ResultRank int `json:"resultRank"` 54 | Learned int `json:"learned"` 55 | Learning int `json:"learning"` 56 | StudentCount int `json:"studentCount"` 57 | ClassTeacher string `json:"classTeacher"` 58 | SignInID int `json:"signInId"` 59 | SignedIn int `json:"signedIn"` 60 | State int `json:"state"` 61 | LastNodeID int `json:"lastNodeId"` 62 | TabVideo bool `json:"tabVideo"` 63 | TabFile bool `json:"tabFile"` 64 | TabVote bool `json:"tabVote"` 65 | TabWork bool `json:"tabWork"` 66 | TabExam bool `json:"tabExam"` 67 | } 68 | type CoursesResult struct { 69 | List []CoursesList `json:"list"` 70 | } 71 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | strategy: 13 | matrix: 14 | platform: [ ubuntu-latest ] 15 | go-version: [ 1.17 ] 16 | name: Build 17 | runs-on: ${{ matrix.platform }} 18 | steps: 19 | 20 | - name: Set up Go 21 | uses: actions/setup-go@v2 22 | with: 23 | go-version: ${{ matrix.go-version }} 24 | 25 | - name: Check out code into the Go module directory 26 | uses: actions/checkout@v2 27 | 28 | - name: Get dependencies 29 | run: | 30 | sudo apt-get update 31 | sudo apt-get -y install gcc-mingw-w64-x86-64 32 | sudo apt-get -y install gcc-arm-linux-gnueabihf libc6-dev-armhf-cross 33 | sudo apt-get -y install gcc-aarch64-linux-gnu libc6-dev-arm64-cross 34 | go get -v -t -d ./... 35 | if [ -f Gopkg.toml ]; then 36 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 37 | dep ensure 38 | fi 39 | - name: Build linux 40 | run: | 41 | CC=gcc CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -trimpath -o aoaostar_mooc_linux_amd64/aoaostar_mooc main.go 42 | CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -trimpath -o aoaostar_mooc_linux_arm64/aoaostar_mooc main.go 43 | CC=arm-linux-gnueabihf-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm go build -trimpath -o aoaostar_mooc_linux_arm/aoaostar_mooc main.go 44 | - name: Build windows 45 | run: | 46 | CC=x86_64-w64-mingw32-gcc CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -trimpath -tags forceposix -o aoaostar_mooc_windows_amd64/aoaostar_mooc.exe main.go 47 | - name: Build linux_386 48 | run: | 49 | sudo apt-get -y install libc6-dev-i386 50 | CC=gcc CGO_ENABLED=1 GOOS=linux GOARCH=386 go build -trimpath -o aoaostar_mooc_linux_386/aoaostar_mooc main.go 51 | 52 | - name: Copy additional files 53 | run: | 54 | ls | grep "aoaostar_mooc_" | awk '{ print "cp sample.config.json "$0"/config.json" | "/bin/bash" }' 55 | ls | grep "aoaostar_mooc_" | awk '{ print "cp -r view "$0"/view" | "/bin/bash" }' 56 | ls | grep "windows" | awk '{ print "cp -r scripts/*.bat "$0"" | "/bin/bash" }' 57 | 58 | - name: Upload artifacts linux_amd64 59 | uses: actions/upload-artifact@v2 60 | with: 61 | name: aoaostar_mooc_linux_amd64 62 | path: aoaostar_mooc_linux_amd64 63 | 64 | - name: Upload artifacts linux_arm64 65 | uses: actions/upload-artifact@v2 66 | with: 67 | name: aoaostar_mooc_linux_arm64 68 | path: aoaostar_mooc_linux_arm64 69 | 70 | - name: Upload artifacts linux_arm 71 | uses: actions/upload-artifact@v2 72 | with: 73 | name: aoaostar_mooc_linux_arm 74 | path: aoaostar_mooc_linux_arm 75 | 76 | - name: Upload artifacts windows_amd64 77 | uses: actions/upload-artifact@v2 78 | with: 79 | name: aoaostar_mooc_windows_amd64 80 | path: aoaostar_mooc_windows_amd64 81 | 82 | - name: Upload artifacts linux_386 83 | uses: actions/upload-artifact@v2 84 | with: 85 | name: aoaostar_mooc_linux_386 86 | path: aoaostar_mooc_linux_386 -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= 2 | github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 3 | github.com/EDDYCJY/fake-useragent v0.2.0 h1:Jcnkk2bgXmDpX0z+ELlUErTkoLb/mxFBNd2YdcpvJBs= 4 | github.com/EDDYCJY/fake-useragent v0.2.0/go.mod h1:5wn3zzlDxhKW6NYknushqinPcAqZcAPHy8lLczCdJdc= 5 | github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U= 6 | github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI= 7 | github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= 8 | github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= 13 | github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= 14 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 15 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 16 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 17 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 18 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 19 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 20 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 21 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 22 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 23 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 24 | golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 25 | golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 26 | golang.org/x/net v0.0.0-20221014081412-f15817d10f9b h1:tvrvnPFcdzp294diPnrdZZZ8XUt2Tyj7svb7X52iDuU= 27 | golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 28 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 29 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 30 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 31 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 32 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 33 | golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY= 34 | golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 35 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 36 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 37 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 38 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 39 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 40 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 41 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= 42 | gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= 43 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 44 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 45 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 46 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 47 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 48 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | 7 | jobs: 8 | release: 9 | strategy: 10 | matrix: 11 | platform: [ubuntu-latest] 12 | go-version: [ 1.17 ] 13 | name: Build 14 | runs-on: ${{ matrix.platform }} 15 | steps: 16 | - name: Set up Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: ${{ matrix.go-version }} 20 | 21 | - name: Get version 22 | id: get_version 23 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} 24 | 25 | - name: Check out code into the Go module directory 26 | uses: actions/checkout@v2 27 | 28 | - name: Get dependencies 29 | run: | 30 | sudo apt-get update 31 | sudo apt-get -y install gcc-mingw-w64-x86-64 32 | sudo apt-get -y install gcc-arm-linux-gnueabihf libc6-dev-armhf-cross 33 | sudo apt-get -y install gcc-aarch64-linux-gnu libc6-dev-arm64-cross 34 | go get -v -t -d ./... 35 | if [ -f Gopkg.toml ]; then 36 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 37 | dep ensure 38 | fi 39 | - name: Build linux 40 | run: | 41 | CC=gcc CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -trimpath -o aoaostar_mooc_linux_amd64/aoaostar_mooc main.go 42 | CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -trimpath -o aoaostar_mooc_linux_arm64/aoaostar_mooc main.go 43 | CC=arm-linux-gnueabihf-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm go build -trimpath -o aoaostar_mooc_linux_arm/aoaostar_mooc main.go 44 | - name: Build windows 45 | run: | 46 | CC=x86_64-w64-mingw32-gcc CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -trimpath -tags forceposix -o aoaostar_mooc_windows_amd64/aoaostar_mooc.exe main.go 47 | - name: Build linux_386 48 | run: | 49 | sudo apt-get -y install libc6-dev-i386 50 | CC=gcc CGO_ENABLED=1 GOOS=linux GOARCH=386 go build -trimpath -o aoaostar_mooc_linux_386/aoaostar_mooc main.go 51 | - name: compress 52 | run: | 53 | ls | grep "aoaostar_mooc_" | awk '{ print "cp sample.config.json "$0"/config.json" | "/bin/bash" }' 54 | ls | grep "aoaostar_mooc_" | awk '{ print "cp -r view "$0"/view" | "/bin/bash" }' 55 | ls | grep "windows" | awk '{ print "cp -r scripts/*.bat "$0"" | "/bin/bash" }' 56 | ls | grep "linux" | awk '{ print "tar -zcvf "$0".tar.gz "$0 | "/bin/bash" }' 57 | zip -r aoaostar_mooc_windows_amd64.zip aoaostar_mooc_windows_amd64 58 | 59 | - name: Build Changelog 60 | id: github_release 61 | uses: mikepenz/release-changelog-builder-action@main 62 | env: 63 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 64 | 65 | - name: Create Release 66 | id: create_release 67 | uses: actions/create-release@v1 68 | env: 69 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 70 | with: 71 | tag_name: ${{ github.ref }} 72 | release_name: Release ${{ github.ref }} 73 | body: ${{steps.github_release.outputs.changelog}} 74 | draft: false 75 | prerelease: false 76 | 77 | - name: Upload aoaostar_mooc_linux_amd64 78 | id: upload-release-linux-amd64 79 | uses: actions/upload-release-asset@v1 80 | env: 81 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 82 | with: 83 | upload_url: ${{ steps.create_release.outputs.upload_url }} 84 | asset_path: aoaostar_mooc_linux_amd64.tar.gz 85 | asset_name: aoaostar_mooc_${{ steps.get_version.outputs.VERSION }}_linux_amd64.tar.gz 86 | asset_content_type: application/gzip 87 | 88 | - name: Upload aoaostar_mooc_linux_arm64 89 | id: upload-release-linux-arm64 90 | uses: actions/upload-release-asset@v1 91 | env: 92 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 93 | with: 94 | upload_url: ${{ steps.create_release.outputs.upload_url }} 95 | asset_path: aoaostar_mooc_linux_arm64.tar.gz 96 | asset_name: aoaostar_mooc_${{ steps.get_version.outputs.VERSION }}_linux_arm64.tar.gz 97 | asset_content_type: application/gzip 98 | 99 | - name: Upload aoaostar_mooc_linux_arm 100 | id: upload-release-linux-arm 101 | uses: actions/upload-release-asset@v1 102 | env: 103 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 104 | with: 105 | upload_url: ${{ steps.create_release.outputs.upload_url }} 106 | asset_path: aoaostar_mooc_linux_arm.tar.gz 107 | asset_name: aoaostar_mooc_${{ steps.get_version.outputs.VERSION }}_linux_arm.tar.gz 108 | asset_content_type: application/gzip 109 | 110 | - name: Upload aoaostar_mooc_linux_386 111 | id: upload-release-linux-386 112 | uses: actions/upload-release-asset@v1 113 | env: 114 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 115 | with: 116 | upload_url: ${{ steps.create_release.outputs.upload_url }} 117 | asset_path: aoaostar_mooc_linux_386.tar.gz 118 | asset_name: aoaostar_mooc_${{ steps.get_version.outputs.VERSION }}_linux_386.tar.gz 119 | asset_content_type: application/gzip 120 | 121 | - name: Upload aoaostar_mooc_windows_amd64 122 | id: upload-release-windows-amd64 123 | uses: actions/upload-release-asset@v1 124 | env: 125 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 126 | with: 127 | upload_url: ${{ steps.create_release.outputs.upload_url }} 128 | asset_path: aoaostar_mooc_windows_amd64.zip 129 | asset_name: aoaostar_mooc_${{ steps.get_version.outputs.VERSION }}_windows_amd64.zip 130 | asset_content_type: application/zip -------------------------------------------------------------------------------- /pkg/yinghua/yinghua.go: -------------------------------------------------------------------------------- 1 | package yinghua 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | browser "github.com/EDDYCJY/fake-useragent" 8 | "github.com/aoaostar/mooc/pkg/config" 9 | "github.com/aoaostar/mooc/pkg/util" 10 | "github.com/aoaostar/mooc/pkg/yinghua/types" 11 | "github.com/go-resty/resty/v2" 12 | "github.com/sirupsen/logrus" 13 | "strconv" 14 | "time" 15 | ) 16 | 17 | type YingHua struct { 18 | User config.User 19 | Courses []types.CoursesList 20 | client *resty.Client 21 | } 22 | 23 | func New(user config.User) *YingHua { 24 | 25 | var client = resty.New() 26 | client.SetBaseURL(user.BaseURL) 27 | client.SetRetryCount(3) 28 | client.SetHeader("user-agent", browser.Mobile()) 29 | return &YingHua{ 30 | User: user, 31 | client: client, 32 | } 33 | 34 | } 35 | 36 | func (i *YingHua) Login() error { 37 | 38 | resp := new(types.LoginResponse) 39 | resp2, err := i.client.R().SetFormData(map[string]string{ 40 | "platform": "Android", 41 | "username": i.User.Username, 42 | "password": i.User.Password, 43 | "pushId": "140fe1da9e67b9c14a7", 44 | "school_id": strconv.Itoa(i.User.SchoolID), 45 | "imgSign": "533560501d19cc30271a850810b09e3e", 46 | "imgCode": "cryd", 47 | }). 48 | SetResult(resp). 49 | Post("/api/login.json") 50 | 51 | if err != nil { 52 | return err 53 | } 54 | if resp.Code != 0 { 55 | return errors.New(resp.Msg) 56 | } 57 | 58 | i.client.SetCookies(resp2.Cookies()) 59 | 60 | i.client.OnBeforeRequest(func(c *resty.Client, req *resty.Request) error { 61 | req.FormData.Set("token", resp.Result.Data.Token) 62 | return nil 63 | }) 64 | 65 | return nil 66 | 67 | } 68 | 69 | func (i *YingHua) GetCourses() error { 70 | 71 | resp := new(types.CoursesResponse) 72 | _, err := i.client.R(). 73 | SetResult(resp). 74 | Post("/api/course.json") 75 | 76 | if err != nil { 77 | return err 78 | } 79 | 80 | if resp.Code != 0 { 81 | return errors.New(resp.Msg) 82 | } 83 | i.Courses = resp.Result.List 84 | return nil 85 | } 86 | 87 | func (i *YingHua) GetChapters(course types.CoursesList) ([]types.ChaptersList, error) { 88 | 89 | resp := new(types.ChaptersResponse) 90 | _, err := i.client.R(). 91 | SetResult(resp). 92 | SetFormData(map[string]string{ 93 | "courseId": strconv.Itoa(course.ID), 94 | }). 95 | Post("/api/course/chapter.json") 96 | 97 | if err != nil { 98 | return nil, err 99 | } 100 | 101 | if resp.Code != 0 { 102 | return nil, errors.New(resp.Msg) 103 | } 104 | return resp.Result.List, nil 105 | } 106 | 107 | func (i *YingHua) StudyCourse(course types.CoursesList) error { 108 | chapters, err := i.GetChapters(course) 109 | if err != nil { 110 | return err 111 | } 112 | for _, chapter := range chapters { 113 | i.StudyChapter(chapter) 114 | } 115 | 116 | return nil 117 | } 118 | 119 | func (i *YingHua) StudyChapter(chapter types.ChaptersList) { 120 | 121 | i.Output(fmt.Sprintf("当前第 %d 章, [%s][chapterId=%d]", chapter.Idx, chapter.Name, chapter.ID)) 122 | for _, node := range chapter.NodeList { 123 | // 试题跳过 124 | if node.TabVideo { 125 | i.StudyNode(node) 126 | } 127 | } 128 | 129 | } 130 | 131 | func (i *YingHua) StudyNode(node types.ChaptersNodeList) { 132 | startStudy: 133 | i.Output(fmt.Sprintf("当前第 %d 课, [%s][nodeId=%d]", node.Idx, node.Name, node.ID)) 134 | var studyTime = 1 135 | var studyId = 0 136 | var nodeProgress = types.NodeVideoData{ 137 | StudyTotal: types.NodeVideoStudyTotal{ 138 | Progress: "0.00", 139 | }, 140 | } 141 | var flag = true 142 | go func() { 143 | for flag { 144 | var err error 145 | nodeProgress, err = i.GetNodeProgress(node) 146 | if err != nil { 147 | i.OutputWith(fmt.Sprintf("%s[nodeId=%d], %s[studyId=%d]", node.Name, node.ID, err.Error(), studyId), logrus.Errorf) 148 | flag = false 149 | break 150 | } 151 | if nodeProgress.StudyTotal.State == "2" { 152 | node.VideoState = 2 153 | break 154 | } 155 | time.Sleep(time.Second * 10) 156 | } 157 | }() 158 | 159 | for node.VideoState != 2 { 160 | if !flag { 161 | goto startStudy 162 | } 163 | 164 | var formData = map[string]string{ 165 | "nodeId": strconv.Itoa(node.ID), 166 | "studyTime": strconv.Itoa(studyTime), 167 | "studyId": strconv.Itoa(studyId), 168 | } 169 | captcha: 170 | var resp = new(types.StudyNodeResponse) 171 | _, err := i.client.R(). 172 | SetFormData(formData). 173 | SetResult(resp). 174 | Post("/api/node/study.json") 175 | if err != nil { 176 | i.OutputWith(fmt.Sprintf("%s[nodeId=%d], %s[studyId=%d][studyTime=%d]", node.Name, node.ID, err.Error(), studyId, studyTime), logrus.Errorf) 177 | continue 178 | } 179 | if resp.Code != 0 { 180 | i.OutputWith(fmt.Sprintf("%s[nodeId=%d], %s[studyId=%d][studyTime=%d]", node.Name, node.ID, resp.Msg, studyId, studyTime), logrus.Errorf) 181 | if resp.NeedCode { 182 | formData["code"] = i.FuckCaptcha() + "_" 183 | goto captcha 184 | } 185 | flag = false 186 | break 187 | } 188 | studyId = resp.Result.Data.StudyID 189 | if nodeProgress.StudyTotal.Progress == "" { 190 | nodeProgress.StudyTotal.Progress = "0.00" 191 | 192 | } 193 | parseFloat, err := strconv.ParseFloat(nodeProgress.StudyTotal.Progress, 64) 194 | 195 | if err != nil { 196 | i.OutputWith(fmt.Sprintf("%s[nodeId=%d], %s[studyId=%d]", node.Name, node.ID, err.Error(), studyId), logrus.Errorf) 197 | continue 198 | } 199 | i.Output(fmt.Sprintf("%s[nodeId=%d], %s[studyId=%d], 当前进度: %.f%%", node.Name, node.ID, resp.Msg, studyId, parseFloat*100)) 200 | studyTime += 10 201 | time.Sleep(time.Second * 10) 202 | } 203 | } 204 | 205 | func (i *YingHua) GetNodeProgress(node types.ChaptersNodeList) (types.NodeVideoData, error) { 206 | 207 | var resp = new(types.NodeVideoResponse) 208 | _, err := i.client.R(). 209 | SetFormData(map[string]string{ 210 | "nodeId": strconv.Itoa(node.ID), 211 | }). 212 | SetResult(resp). 213 | Post("/api/node/video.json") 214 | if err != nil { 215 | i.OutputWith(fmt.Sprintf("%s[nodeId=%d], %s", node.Name, node.ID, err.Error()), logrus.Errorf) 216 | return resp.Result.Data, nil 217 | } 218 | if resp.Code != 0 { 219 | return resp.Result.Data, errors.New(resp.Msg) 220 | } 221 | return resp.Result.Data, nil 222 | } 223 | 224 | func (i *YingHua) FuckCaptcha() string { 225 | 226 | i.Output("正在识别验证码") 227 | response, err := i.client.R(). 228 | Get(fmt.Sprintf("/service/code/aa?t=%d", time.Now().UnixNano())) 229 | 230 | if err != nil { 231 | i.OutputWith(err.Error(), logrus.Errorf) 232 | } 233 | var resp = new(types.Captcha) 234 | client := resty.New() 235 | _, err = client.R(). 236 | SetFileReader("file", "image.png", bytes.NewReader(response.Body())). 237 | SetResult(resp). 238 | Post("https://api.opop.vip/captcha/recognize") 239 | 240 | if err != nil { 241 | i.OutputWith(err.Error(), logrus.Errorf) 242 | } 243 | if resp.Status != "ok" { 244 | i.OutputWith(resp.Message, logrus.Errorf) 245 | } 246 | s := resp.Data.(string) 247 | i.Output(fmt.Sprintf("验证码识别成功: %s", s)) 248 | return s 249 | } 250 | func (i *YingHua) Output(message string) { 251 | i.OutputWith(message, logrus.Infof) 252 | } 253 | 254 | func (i *YingHua) OutputWith(message string, writer func(format string, args ...interface{})) { 255 | writer("[协程ID=%d][%s] %s", util.GetGid(), i.User.Username, message) 256 | } 257 | --------------------------------------------------------------------------------