├── 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 | 
11 |
12 | ### web端
13 |
14 | 
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 |
--------------------------------------------------------------------------------