Skip to content

Commit

Permalink
feat: 添加蹲课
Browse files Browse the repository at this point in the history
  • Loading branch information
cr4n5 committed Jan 21, 2025
1 parent 25340fe commit c39d2d9
Show file tree
Hide file tree
Showing 11 changed files with 362 additions and 13 deletions.
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

## 简介

支持主修,选修,体育课程,特殊课程
- 支持主修,选修,体育课程,特殊课程
- 支持蹲课

> [!TIP]
>
Expand Down Expand Up @@ -48,7 +49,7 @@ go build
"password": "xxxxxxxx",
"level:" : "1" //优先级
}, // 0<1 所以优先使用cas登录 所以0比1大 数学天才
"cookies": {
"cookies": { //若 JSESSIONID为空 或 route为空 或 enabled为0,则将不会使用cookies登录
"JSESSIONID": "",// 每次登录cookie参数都会自动更新
"route": "",
"enabled": "1"//如若登录过期,将enabled改为0,将不会使用cookies登录
Expand All @@ -66,6 +67,17 @@ go build
"(2024-2025-1)-C2892008-02" : "1",
"(2024-2025-1)-W0001321-06" : "0"
},
"wait_course": {
"interval": 60, //查询课程间隔时间,单位秒
"enabled": "0" //是否开启蹲课,开启后将蹲course中值为1的课程,不再进行抢课
},
"smtp_email": { //邮件通知,开启后将会在蹲选课成功后发送邮件通知
"host": "smtp.qq.com", //smtp服务器
"username": "...@qq.com", //发送邮件的邮箱
"password": "xxxxxxxx", //发送邮件的邮箱授权码
"to": "...@qq.com", //接收邮件的邮箱
"enabled": "0" //是否开启邮件通知
},
//课程按顺序执行
"start_time": "2024-07-25 12:00:00",//程序开始时间
}
Expand Down
24 changes: 24 additions & 0 deletions client/req.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,3 +267,27 @@ func (req *CancelCourseReq) ToFormData() url.Values {
"xkxqm": {req.Xkxqm},
}
}

// SearchCourseReq 搜索课程请求
type SearchCourseReq struct {
Xkxnm string
Xkxqm string
Kklxdm string
Jspage string
Kspage string
Yllist string // 是否有余量
Filterlist string // 搜索内容
}

// ToFormData 转换为表单数据
func (req *SearchCourseReq) ToFormData() url.Values {
return url.Values{
"xkxnm": {req.Xkxnm},
"xkxqm": {req.Xkxqm},
"kklxdm": {req.Kklxdm},
"jspage": {req.Jspage},
"kspage": {req.Kspage},
"yl_list[0]": {req.Yllist},
"filter_list[0]": {req.Filterlist},
}
}
6 changes: 6 additions & 0 deletions client/resp.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,9 @@ type SelectCourseResq struct {
Flag string `json:"flag"`
Msg string `json:"msg"`
}

type SearchCourseResp struct {
TmpList []struct {
Jxbmc string `json:"jxbmc"`
} `json:"tmpList"`
}
32 changes: 27 additions & 5 deletions client/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func (c *Client) GetCourse(req *GetCourseReq) (*GetCourseResp, error) {
if err != nil {
return nil, err
}
if strings.Contains(string(result), "登录") {
if strings.Contains(string(result), "统一身份认证") {
return nil, errors.New("可能登录过期")
}
// 检验是否可以获取课程
Expand Down Expand Up @@ -241,7 +241,7 @@ func (c *Client) GetClientBodyConfig() error {
if err != nil {
return err
}
if strings.Contains(string(result), "登录") {
if strings.Contains(string(result), "统一身份认证") {
return errors.New("可能登录过期")
}
// 检验是否可以获取选课配置
Expand Down Expand Up @@ -270,7 +270,7 @@ func (c *Client) GetDoJxbId(req *GetDoJxbIdReq) ([]GetDoJxbIdResp, error) {
if err != nil {
return nil, err
}
if strings.Contains(string(result), "登录") {
if strings.Contains(string(result), "统一身份认证") {
return nil, errors.New("可能登录过期")
}
// 解析do_jxb_id
Expand All @@ -292,7 +292,7 @@ func (c *Client) SelectCourse(req *SelectCourseReq) (*SelectCourseResq, error) {
if err != nil {
return nil, err
}
if strings.Contains(string(result), "登录") {
if strings.Contains(string(result), "统一身份认证") {
return nil, errors.New("可能登录过期")
}
// 解析选课结果
Expand All @@ -314,9 +314,31 @@ func (c *Client) CancelCourse(req *CancelCourseReq) (string, error) {
if err != nil {
return "", err
}
if strings.Contains(string(result), "登录") {
if strings.Contains(string(result), "统一身份认证") {
return "", errors.New("可能登录过期")
}

return string(result), nil
}

// SearchCourse 搜索课程
func (c *Client) SearchCourse(req *SearchCourseReq) (*SearchCourseResp, error) {
url := "https://newjw.hdu.edu.cn/jwglxt/xsxk/zzxkyzb_cxZzxkYzbPartDisplay.html?gnmkdm=N253512"
// 搜索课程
formData := req.ToFormData()
result, _, err := c.Post(url, formData.Encode())
if err != nil {
return nil, err
}
if strings.Contains(string(result), "统一身份认证") {
return nil, errors.New("可能登录过期")
}
// 解析搜索结果
var searchCourseResp SearchCourseResp
err = json.Unmarshal(result, &searchCourseResp)
if err != nil {
return nil, err
}

return &searchCourseResp, nil
}
8 changes: 6 additions & 2 deletions cmd/HDU-KillCourse/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,18 @@ func main() {
signal.Notify(stopChan, syscall.SIGINT, syscall.SIGTERM)

// 选退课
go KillCourse(cancelCtx, c, cfg, courses)
if cfg.WaitCourse.Enabled == "1" {
go WaitCourse(cancelCtx, c, cfg, courses)
} else {
go KillCourse(cancelCtx, c, cfg, courses)
}

select {
case <-stopChan:
log.Info("收到终止信号,正在退出...")
cancel()
case <-channel:
log.Info("退选课程已完成,正在退出...")
log.Info("此程序已完成,正在退出...")
cancel()
}
}
208 changes: 208 additions & 0 deletions cmd/HDU-KillCourse/waitCourse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package main

import (
"context"
"errors"
"github.com/cr4n5/HDU-KillCourse/client"
"github.com/cr4n5/HDU-KillCourse/config"
"github.com/cr4n5/HDU-KillCourse/log"
"github.com/cr4n5/HDU-KillCourse/util"
"time"
)

// StartWaitCourse 开始蹲课
func StartWaitCourse(ctx context.Context, c *client.Client, cfg *config.Config, courses *config.Course, CourseName string, waitCourseChannel chan string) {
defer func() {
waitCourseChannel <- "完成"
}()

firstRun := true

for {
if firstRun {
firstRun = false
} else {
select {
case <-ctx.Done():
return
case <-time.After(time.Duration(cfg.WaitCourse.Interval) * time.Second):
}
}

// 检验是否有余量
isOk, err := GetIsCourseOk(c, cfg, courses, CourseName)
if err != nil {
log.Error(CourseName+"查询失败: ", err)
if err.Error() == "可能登录过期" {
waitCourseChannel <- "登录过期"
return
}
SendEmail(cfg, "查询失败", CourseName+"查询失败,将会继续蹲课: "+err.Error())
continue
}

if isOk {
// 选课
err := HandleCourse(c, cfg, courses, CourseName, "1")
if err != nil {
log.Error(CourseName+"选课失败: ", err)
if err.Error() == "可能登录过期" {
waitCourseChannel <- "登录过期"
return
}
SendEmail(cfg, "蹲选课失败", CourseName+"选课失败,将会继续蹲课: "+err.Error())
continue
}

log.Info(CourseName + "蹲选课成功")
// 发送邮件
SendEmail(cfg, "蹲选课成功", CourseName+"选课成功?(,请自行查看确认")

// 将此CourseName设置为0
cfg.Course.Set(CourseName, "0")

return
}
}
}

// SendEmail 发送邮件
func SendEmail(cfg *config.Config, subject string, body string) {
if cfg.SmtpEmail.Enabled == "1" {
err := util.SendEmail(cfg.SmtpEmail.Host, cfg.SmtpEmail.Username, cfg.SmtpEmail.Password, cfg.SmtpEmail.To, subject, body)
if err != nil {
log.Error("发送邮件失败: ", err)
}
}
}

// GetIsCourseOk 检验是否有余量
func GetIsCourseOk(c *client.Client, cfg *config.Config, course *config.Course, CourseName string) (bool, error) {
for _, v := range course.Items {
if v.Jxbmc == CourseName {
// 更改Kklxdm
Kklxdm := v.Kklxmc
if Kklxdm == "主修课程" {
Kklxdm = "01"
} else if Kklxdm == "通识选修课" {
Kklxdm = "10"
} else if Kklxdm == "体育分项" {
Kklxdm = "05"
} else if Kklxdm == "特殊课程" {
Kklxdm = "09"
} else {
return false, errors.New("课程类型错误")
}

// 设置Xqm
Xqm := cfg.Time.XueQi
if Xqm == "1" {
Xqm = "3"
} else if Xqm == "2" {
Xqm = "12"
} else {
return false, errors.New("学期格式错误")
}

// 获取课程是否有余量
req := &client.SearchCourseReq{
Xkxnm: cfg.Time.XueNian,
Xkxqm: Xqm,
Kklxdm: Kklxdm,
Kspage: "1",
Jspage: "10",
Yllist: "1",
Filterlist: CourseName,
}

// 发送请求
result, err := c.SearchCourse(req)
if err != nil {
return false, err
}

// 检验是否有余量, TmpList是否长度为0
if len(result.TmpList) == 0 {
log.Info(CourseName + "" + v.Kcmc + ": " + "课程无余量")
return false, nil
} else {
log.Info(CourseName + "" + v.Kcmc + ": " + "课程有余量")
return true, nil
}

}
}
return false, errors.New(CourseName + "课程不存在")
}

// WaitCourse 蹲课
func WaitCourse(ctx context.Context, c *client.Client, cfg *config.Config, course *config.Course) {
defer func() {
channel <- "完成"
}()

log.Info("开始蹲课...")

// 关闭Cookies
cfg.Cookies.Enabled = "0"

// 获取选课配置
err := ReadClientBodyConfig(c)
if err != nil {
err = c.GetClientBodyConfig()
if err != nil {
log.Error("获取选课配置失败: ", err)
return
}
}
log.Info("选课配置获取成功")

for {
waitCourseChannel := make(chan string)
numWaitCourse := 0
waitCourseCtx, cancel := context.WithCancel(ctx)

// 蹲课
for _, k := range cfg.Course.Keys() {
v, _ := cfg.Course.Get(k)
// 开始蹲课
if v == "1" {
numWaitCourse++
go StartWaitCourse(waitCourseCtx, c, cfg, course, k, waitCourseChannel)
}
}

// 等待蹲课结束
outerLoop:
for {
select {
case <-ctx.Done():
cancel()
return
case message := <-waitCourseChannel:
if message == "登录过期" {
log.Error("登录过期")
cancel()
SendEmail(cfg, "登录过期", "登录过期,自动重新登录")
close(waitCourseChannel)

log.Info("重新登录...")
// 重新登录
c, err = Login(cfg)
if err != nil {
log.Error("登录失败...")
SendEmail(cfg, "登录失败", "登录失败,程序停止,请检查")
return
}
break outerLoop
}

numWaitCourse--
if numWaitCourse == 0 {
cancel()
return
}
}
}
}
}
11 changes: 11 additions & 0 deletions config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,16 @@
"(2024-2025-1)-C2892008-02" : "1",
"(2024-2025-1)-W0001321-06" : "0"
},
"wait_course": {
"interval": 60,
"enabled": "0"
},
"smtp_email": {
"host": "smtp.qq.com",
"username": "...@qq.com",
"password": "xxxxxxxx",
"to": "...@qq.com",
"enabled": "0"
},
"start_time": "2024-07-25 12:00:00"
}
Loading

0 comments on commit c39d2d9

Please sign in to comment.