feat(grpc): add support for fetching empty classrooms
All checks were successful
Build / build (push) Successful in 1m31s
All checks were successful
Build / build (push) Successful in 1m31s
Implement the `TASK_TYPE_GET_EMPTY_CLASSROOM` task type to allow users to fetch available classroom information. - Add `TASK_TYPE_GET_EMPTY_CLASSROOM` to the `TaskType` enum in protobuf. - Define `GetEmptyClassroomPayload` and `EmptyClassroomResultData` messages. - Implement `getEmptyClassrooms`
This commit is contained in:
@@ -77,10 +77,11 @@ func (c *Client) register() error {
|
||||
RunnerId: c.runnerID,
|
||||
Version: c.version,
|
||||
Capabilities: map[string]string{
|
||||
"TASK_TYPE_GET_SCHEDULE": "true",
|
||||
"TASK_TYPE_GET_GRADES": "true",
|
||||
"TASK_TYPE_GET_EXAMS": "true",
|
||||
"TASK_TYPE_LOGIN": "true",
|
||||
"TASK_TYPE_GET_SCHEDULE": "true",
|
||||
"TASK_TYPE_GET_GRADES": "true",
|
||||
"TASK_TYPE_GET_EXAMS": "true",
|
||||
"TASK_TYPE_LOGIN": "true",
|
||||
"TASK_TYPE_GET_EMPTY_CLASSROOM": "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -58,6 +58,8 @@ func (h *TaskHandler) Execute(task *pb.Task) *pb.TaskResult {
|
||||
data, err = h.getExams(context.Background(), task.Payload)
|
||||
case pb.TaskType_TASK_TYPE_LOGIN:
|
||||
data, err = h.login(context.Background(), task.Payload)
|
||||
case pb.TaskType_TASK_TYPE_GET_EMPTY_CLASSROOM:
|
||||
data, err = h.getEmptyClassrooms(context.Background(), task.Payload)
|
||||
default:
|
||||
result.Status = pb.TaskStatus_TASK_STATUS_FAILED
|
||||
result.ErrorMessage = fmt.Sprintf("未知的任务类型: %v", task.Type)
|
||||
@@ -383,3 +385,55 @@ func convertExams(exams []parser.ExamEntry) []*pb.Exam {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (h *TaskHandler) getEmptyClassrooms(ctx context.Context, payload []byte) ([]byte, error) {
|
||||
var req pb.GetEmptyClassroomPayload
|
||||
if err := json.Unmarshal(payload, &req); err != nil {
|
||||
return nil, apperr.New("parse_payload", err, "解析任务载荷失败")
|
||||
}
|
||||
|
||||
log := slog.With("username", req.Username, "semester", req.Semester)
|
||||
log.Info("获取空教室任务")
|
||||
|
||||
httpClient, err := createHTTPClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := config.Get()
|
||||
setTWFIDCookie(httpClient, cfg.BaseURL, "")
|
||||
|
||||
if err := executeLogin(ctx, httpClient, req.Username, req.Password); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
classrooms, err := parser.FetchEmptyClassrooms(httpClient, req.Semester, req.WeekStart, req.WeekEnd, req.CampusCode)
|
||||
if err != nil {
|
||||
return nil, apperr.New("fetch_empty_classrooms", err, "获取空教室失败")
|
||||
}
|
||||
|
||||
pbClassrooms := convertEmptyClassrooms(classrooms)
|
||||
|
||||
result := &pb.EmptyClassroomResultData{
|
||||
StudentId: req.Username,
|
||||
Semester: req.Semester,
|
||||
Classrooms: pbClassrooms,
|
||||
}
|
||||
|
||||
log.Info("空教室获取成功", "classroom_count", len(pbClassrooms))
|
||||
return json.Marshal(result)
|
||||
}
|
||||
|
||||
func convertEmptyClassrooms(entries []parser.EmptyClassroomEntry) []*pb.EmptyClassroomEntry {
|
||||
result := make([]*pb.EmptyClassroomEntry, 0, len(entries))
|
||||
for _, e := range entries {
|
||||
result = append(result, &pb.EmptyClassroomEntry{
|
||||
Classroom: e.Classroom,
|
||||
Weekday: e.Weekday,
|
||||
Periods: e.Periods,
|
||||
Term: e.Term,
|
||||
Weeks: e.Weeks,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
256
parser/empty_classroom.go
Normal file
256
parser/empty_classroom.go
Normal file
@@ -0,0 +1,256 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"schedule_converter/client"
|
||||
"schedule_converter/config"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
)
|
||||
|
||||
type EmptyClassroomEntry struct {
|
||||
Classroom string `json:"classroom"`
|
||||
Weekday string `json:"weekday"`
|
||||
Periods string `json:"periods"`
|
||||
Term string `json:"term"`
|
||||
Weeks string `json:"weeks"`
|
||||
}
|
||||
|
||||
type BuildingInfo struct {
|
||||
DM string `json:"dm"`
|
||||
MC string `json:"mc"`
|
||||
}
|
||||
|
||||
type VenueInfo struct {
|
||||
DM string `json:"dm"`
|
||||
MC string `json:"mc"`
|
||||
}
|
||||
|
||||
func FetchEmptyClassrooms(httpClient *http.Client, semester, weekStart, weekEnd, campusCode string) ([]EmptyClassroomEntry, error) {
|
||||
url := config.BaseURL + "/kjscx/queryKjs"
|
||||
fmt.Printf("[空教室] 正在获取空教室数据 (学期=%s, 周次=%s-%s)...\n", semester, weekStart, weekEnd)
|
||||
|
||||
// First GET to establish session
|
||||
resp, err := httpClient.Get(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("访问空教室页面失败: %w", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
// POST with query parameters
|
||||
data := fmt.Sprintf("pageXnxq=%s&pageZc1=%s&pageZc2=%s&pageXiaoqu=%s",
|
||||
semester, weekStart, weekEnd, campusCode)
|
||||
req, err := http.NewRequest("POST", url, strings.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header = client.GetHeadersWithContentType("application/x-www-form-urlencoded")
|
||||
req.Header.Set("Referer", url)
|
||||
|
||||
resp, err = httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("查询空教室失败: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
html, err := decodeResponseBody(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entries, err := ParseEmptyClassroomHTML(html, semester, weekStart, weekEnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fmt.Printf("[空教室] 共获取 %d 条空教室记录\n", len(entries))
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func ParseEmptyClassroomHTML(html, semester, weekStart, weekEnd string) ([]EmptyClassroomEntry, error) {
|
||||
if html == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Clean nbsp entities
|
||||
cleaned := strings.ReplaceAll(html, " ", "")
|
||||
cleaned = strings.ReplaceAll(cleaned, "&", "&")
|
||||
|
||||
doc, err := goquery.NewDocumentFromReader(strings.NewReader(cleaned))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dayNames := [7]string{"星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"}
|
||||
periodLabels := [6]string{"1,2", "3,4", "5,6", "7,8", "9,10", "11,12"}
|
||||
|
||||
var classrooms []EmptyClassroomEntry
|
||||
|
||||
doc.Find("table").Each(func(_ int, table *goquery.Selection) {
|
||||
rows := table.Find("tr")
|
||||
if rows.Length() < 3 {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if this table has "星期一" in header
|
||||
headerText := ""
|
||||
table.Find("th").Each(func(_ int, th *goquery.Selection) {
|
||||
headerText += th.Text()
|
||||
})
|
||||
if !strings.Contains(headerText, "星期一") {
|
||||
return
|
||||
}
|
||||
|
||||
// Parse period row (second row) to map column index to (day, period)
|
||||
periodRow := rows.Eq(1)
|
||||
periodCells := periodRow.Find("td")
|
||||
periodCellTexts := make([]string, periodCells.Length())
|
||||
periodCells.Each(func(i int, cell *goquery.Selection) {
|
||||
periodCellTexts[i] = strings.TrimSpace(cell.Text())
|
||||
})
|
||||
|
||||
colToDayPeriod := make(map[int][2]string)
|
||||
colIdx := 1
|
||||
for dayIdx := 0; dayIdx < 7; dayIdx++ {
|
||||
for periodIdx := 0; periodIdx < 6; periodIdx++ {
|
||||
var periodText string
|
||||
if colIdx < len(periodCellTexts) {
|
||||
periodText = periodCellTexts[colIdx]
|
||||
} else {
|
||||
periodText = periodLabels[periodIdx]
|
||||
}
|
||||
colToDayPeriod[colIdx] = [2]string{dayNames[dayIdx], periodText}
|
||||
colIdx++
|
||||
}
|
||||
}
|
||||
|
||||
// Data rows start from row 2+
|
||||
rows.FilterFunction(func(_ int, row *goquery.Selection) bool {
|
||||
return row.Index() >= 2
|
||||
}).Each(func(_ int, row *goquery.Selection) {
|
||||
cells := row.Find("td")
|
||||
if cells.Length() < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
classroomName := strings.TrimSpace(cells.First().Text())
|
||||
if classroomName == "" || len(classroomName) < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
var slots []string
|
||||
var weekdays []string
|
||||
weekdaySet := make(map[string]bool)
|
||||
|
||||
for colIdx := 1; colIdx < cells.Length(); colIdx++ {
|
||||
cell := cells.Eq(colIdx)
|
||||
// Check if cell has kjs_icon div (indicates occupied, not empty)
|
||||
hasIcon := false
|
||||
cell.Find("div").Each(func(_ int, div *goquery.Selection) {
|
||||
if classAttr, exists := div.Attr("class"); exists && strings.Contains(classAttr, "kjs_icon") {
|
||||
hasIcon = true
|
||||
}
|
||||
})
|
||||
|
||||
if !hasIcon && colToDayPeriod[colIdx][0] != "" {
|
||||
dayName := colToDayPeriod[colIdx][0]
|
||||
periodText := colToDayPeriod[colIdx][1]
|
||||
var slot string
|
||||
if periodText != "" {
|
||||
slot = fmt.Sprintf("%s 第%s节", dayName, periodText)
|
||||
} else {
|
||||
slot = dayName
|
||||
}
|
||||
slots = append(slots, slot)
|
||||
if !weekdaySet[dayName] {
|
||||
weekdaySet[dayName] = true
|
||||
weekdays = append(weekdays, dayName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(slots) > 0 {
|
||||
weekStr := ""
|
||||
if weekStart != "" && weekEnd != "" {
|
||||
weekStr = fmt.Sprintf("%s-%s周", weekStart, weekEnd)
|
||||
}
|
||||
entry := EmptyClassroomEntry{
|
||||
Classroom: classroomName,
|
||||
Weekday: strings.Join(weekdays, ", "),
|
||||
Periods: strings.Join(slots, "\n"),
|
||||
Term: semester,
|
||||
Weeks: weekStr,
|
||||
}
|
||||
classrooms = append(classrooms, entry)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return classrooms, nil
|
||||
}
|
||||
|
||||
func FetchBuildings(httpClient *http.Client, campusCode string) ([]BuildingInfo, error) {
|
||||
url := config.BaseURL + "/kjscx/queryJxlListBySjid"
|
||||
fmt.Printf("[空教室] 正在获取楼号列表 (校区=%s)...\n", campusCode)
|
||||
|
||||
data := fmt.Sprintf("id=%s", campusCode)
|
||||
req, err := http.NewRequest("POST", url, strings.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header = client.GetHeadersWithContentType("application/x-www-form-urlencoded")
|
||||
req.Header.Set("Referer", config.BaseURL+"/kjscx/queryKjs")
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取楼号列表失败: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("获取楼号列表失败, 状态码: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var buildings []BuildingInfo
|
||||
if err := decodeJSONResponse(resp, &buildings); err != nil {
|
||||
return nil, fmt.Errorf("解析楼号列表失败: %w", err)
|
||||
}
|
||||
|
||||
return buildings, nil
|
||||
}
|
||||
|
||||
func FetchVenues(httpClient *http.Client, buildingCode string) ([]VenueInfo, error) {
|
||||
url := config.BaseURL + "/kjscx/queryJxcdListBySjid"
|
||||
fmt.Printf("[空教室] 正在获取场地列表 (楼号=%s)...\n", buildingCode)
|
||||
|
||||
data := fmt.Sprintf("id=%s", buildingCode)
|
||||
req, err := http.NewRequest("POST", url, strings.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header = client.GetHeadersWithContentType("application/x-www-form-urlencoded")
|
||||
req.Header.Set("Referer", config.BaseURL+"/kjscx/queryKjs")
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取场地列表失败: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("获取场地列表失败, 状态码: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var venues []VenueInfo
|
||||
if err := decodeJSONResponse(resp, &venues); err != nil {
|
||||
return nil, fmt.Errorf("解析场地列表失败: %w", err)
|
||||
}
|
||||
|
||||
return venues, nil
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -71,4 +72,12 @@ func isValidUTF8(data []byte) bool {
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func decodeJSONResponse(resp *http.Response, v any) error {
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.NewDecoder(bytes.NewReader(body)).Decode(v)
|
||||
}
|
||||
@@ -25,12 +25,13 @@ const (
|
||||
type TaskType int32
|
||||
|
||||
const (
|
||||
TaskType_TASK_TYPE_UNKNOWN TaskType = 0 // 未知类型
|
||||
TaskType_TASK_TYPE_LOGIN TaskType = 1 // 登录验证
|
||||
TaskType_TASK_TYPE_GET_SCHEDULE TaskType = 2 // 获取课表
|
||||
TaskType_TASK_TYPE_GET_GRADES TaskType = 3 // 获取成绩
|
||||
TaskType_TASK_TYPE_GET_EXAMS TaskType = 4 // 获取考试安排
|
||||
TaskType_TASK_TYPE_GET_USER_INFO TaskType = 5 // 获取用户信息
|
||||
TaskType_TASK_TYPE_UNKNOWN TaskType = 0 // 未知类型
|
||||
TaskType_TASK_TYPE_LOGIN TaskType = 1 // 登录验证
|
||||
TaskType_TASK_TYPE_GET_SCHEDULE TaskType = 2 // 获取课表
|
||||
TaskType_TASK_TYPE_GET_GRADES TaskType = 3 // 获取成绩
|
||||
TaskType_TASK_TYPE_GET_EXAMS TaskType = 4 // 获取考试安排
|
||||
TaskType_TASK_TYPE_GET_USER_INFO TaskType = 5 // 获取用户信息
|
||||
TaskType_TASK_TYPE_GET_EMPTY_CLASSROOM TaskType = 6 // 获取空教室
|
||||
)
|
||||
|
||||
// Enum value maps for TaskType.
|
||||
@@ -42,14 +43,16 @@ var (
|
||||
3: "TASK_TYPE_GET_GRADES",
|
||||
4: "TASK_TYPE_GET_EXAMS",
|
||||
5: "TASK_TYPE_GET_USER_INFO",
|
||||
6: "TASK_TYPE_GET_EMPTY_CLASSROOM",
|
||||
}
|
||||
TaskType_value = map[string]int32{
|
||||
"TASK_TYPE_UNKNOWN": 0,
|
||||
"TASK_TYPE_LOGIN": 1,
|
||||
"TASK_TYPE_GET_SCHEDULE": 2,
|
||||
"TASK_TYPE_GET_GRADES": 3,
|
||||
"TASK_TYPE_GET_EXAMS": 4,
|
||||
"TASK_TYPE_GET_USER_INFO": 5,
|
||||
"TASK_TYPE_UNKNOWN": 0,
|
||||
"TASK_TYPE_LOGIN": 1,
|
||||
"TASK_TYPE_GET_SCHEDULE": 2,
|
||||
"TASK_TYPE_GET_GRADES": 3,
|
||||
"TASK_TYPE_GET_EXAMS": 4,
|
||||
"TASK_TYPE_GET_USER_INFO": 5,
|
||||
"TASK_TYPE_GET_EMPTY_CLASSROOM": 6,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1711,6 +1714,237 @@ func (x *UserInfoResultData) GetGrade() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetEmptyClassroomPayload 获取空教室任务载荷
|
||||
type GetEmptyClassroomPayload struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` // 学号
|
||||
Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` // 密码
|
||||
Semester string `protobuf:"bytes,3,opt,name=semester,proto3" json:"semester,omitempty"` // 学期
|
||||
WeekStart string `protobuf:"bytes,4,opt,name=week_start,json=weekStart,proto3" json:"week_start,omitempty"` // 起始周次
|
||||
WeekEnd string `protobuf:"bytes,5,opt,name=week_end,json=weekEnd,proto3" json:"week_end,omitempty"` // 结束周次
|
||||
CampusCode string `protobuf:"bytes,6,opt,name=campus_code,json=campusCode,proto3" json:"campus_code,omitempty"` // 校区代码
|
||||
BuildingCode string `protobuf:"bytes,7,opt,name=building_code,json=buildingCode,proto3" json:"building_code,omitempty"` // 楼号代码(可选)
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GetEmptyClassroomPayload) Reset() {
|
||||
*x = GetEmptyClassroomPayload{}
|
||||
mi := &file_proto_runner_runner_proto_msgTypes[21]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GetEmptyClassroomPayload) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetEmptyClassroomPayload) ProtoMessage() {}
|
||||
|
||||
func (x *GetEmptyClassroomPayload) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_runner_runner_proto_msgTypes[21]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetEmptyClassroomPayload.ProtoReflect.Descriptor instead.
|
||||
func (*GetEmptyClassroomPayload) Descriptor() ([]byte, []int) {
|
||||
return file_proto_runner_runner_proto_rawDescGZIP(), []int{21}
|
||||
}
|
||||
|
||||
func (x *GetEmptyClassroomPayload) GetUsername() string {
|
||||
if x != nil {
|
||||
return x.Username
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GetEmptyClassroomPayload) GetPassword() string {
|
||||
if x != nil {
|
||||
return x.Password
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GetEmptyClassroomPayload) GetSemester() string {
|
||||
if x != nil {
|
||||
return x.Semester
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GetEmptyClassroomPayload) GetWeekStart() string {
|
||||
if x != nil {
|
||||
return x.WeekStart
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GetEmptyClassroomPayload) GetWeekEnd() string {
|
||||
if x != nil {
|
||||
return x.WeekEnd
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GetEmptyClassroomPayload) GetCampusCode() string {
|
||||
if x != nil {
|
||||
return x.CampusCode
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GetEmptyClassroomPayload) GetBuildingCode() string {
|
||||
if x != nil {
|
||||
return x.BuildingCode
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// EmptyClassroomResultData 空教室结果数据
|
||||
type EmptyClassroomResultData struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Semester string `protobuf:"bytes,1,opt,name=semester,proto3" json:"semester,omitempty"` // 学期
|
||||
StudentId string `protobuf:"bytes,2,opt,name=student_id,json=studentId,proto3" json:"student_id,omitempty"` // 学号
|
||||
Classrooms []*EmptyClassroomEntry `protobuf:"bytes,3,rep,name=classrooms,proto3" json:"classrooms,omitempty"` // 空教室列表
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *EmptyClassroomResultData) Reset() {
|
||||
*x = EmptyClassroomResultData{}
|
||||
mi := &file_proto_runner_runner_proto_msgTypes[22]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *EmptyClassroomResultData) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*EmptyClassroomResultData) ProtoMessage() {}
|
||||
|
||||
func (x *EmptyClassroomResultData) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_runner_runner_proto_msgTypes[22]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use EmptyClassroomResultData.ProtoReflect.Descriptor instead.
|
||||
func (*EmptyClassroomResultData) Descriptor() ([]byte, []int) {
|
||||
return file_proto_runner_runner_proto_rawDescGZIP(), []int{22}
|
||||
}
|
||||
|
||||
func (x *EmptyClassroomResultData) GetSemester() string {
|
||||
if x != nil {
|
||||
return x.Semester
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *EmptyClassroomResultData) GetStudentId() string {
|
||||
if x != nil {
|
||||
return x.StudentId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *EmptyClassroomResultData) GetClassrooms() []*EmptyClassroomEntry {
|
||||
if x != nil {
|
||||
return x.Classrooms
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EmptyClassroomEntry 空教室条目
|
||||
type EmptyClassroomEntry struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Classroom string `protobuf:"bytes,1,opt,name=classroom,proto3" json:"classroom,omitempty"` // 教室名称
|
||||
Weekday string `protobuf:"bytes,2,opt,name=weekday,proto3" json:"weekday,omitempty"` // 星期几
|
||||
Periods string `protobuf:"bytes,3,opt,name=periods,proto3" json:"periods,omitempty"` // 可用节次
|
||||
Term string `protobuf:"bytes,4,opt,name=term,proto3" json:"term,omitempty"` // 学期描述
|
||||
Weeks string `protobuf:"bytes,5,opt,name=weeks,proto3" json:"weeks,omitempty"` // 周次范围
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *EmptyClassroomEntry) Reset() {
|
||||
*x = EmptyClassroomEntry{}
|
||||
mi := &file_proto_runner_runner_proto_msgTypes[23]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *EmptyClassroomEntry) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*EmptyClassroomEntry) ProtoMessage() {}
|
||||
|
||||
func (x *EmptyClassroomEntry) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_runner_runner_proto_msgTypes[23]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use EmptyClassroomEntry.ProtoReflect.Descriptor instead.
|
||||
func (*EmptyClassroomEntry) Descriptor() ([]byte, []int) {
|
||||
return file_proto_runner_runner_proto_rawDescGZIP(), []int{23}
|
||||
}
|
||||
|
||||
func (x *EmptyClassroomEntry) GetClassroom() string {
|
||||
if x != nil {
|
||||
return x.Classroom
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *EmptyClassroomEntry) GetWeekday() string {
|
||||
if x != nil {
|
||||
return x.Weekday
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *EmptyClassroomEntry) GetPeriods() string {
|
||||
if x != nil {
|
||||
return x.Periods
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *EmptyClassroomEntry) GetTerm() string {
|
||||
if x != nil {
|
||||
return x.Term
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *EmptyClassroomEntry) GetWeeks() string {
|
||||
if x != nil {
|
||||
return x.Weeks
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_proto_runner_runner_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_proto_runner_runner_proto_rawDesc = "" +
|
||||
@@ -1849,14 +2083,38 @@ const file_proto_runner_runner_proto_rawDesc = "" +
|
||||
"\x05major\x18\x05 \x01(\tR\x05major\x12\x1d\n" +
|
||||
"\n" +
|
||||
"class_name\x18\x06 \x01(\tR\tclassName\x12\x14\n" +
|
||||
"\x05grade\x18\a \x01(\tR\x05grade*\xa2\x01\n" +
|
||||
"\x05grade\x18\a \x01(\tR\x05grade\"\xee\x01\n" +
|
||||
"\x18GetEmptyClassroomPayload\x12\x1a\n" +
|
||||
"\busername\x18\x01 \x01(\tR\busername\x12\x1a\n" +
|
||||
"\bpassword\x18\x02 \x01(\tR\bpassword\x12\x1a\n" +
|
||||
"\bsemester\x18\x03 \x01(\tR\bsemester\x12\x1d\n" +
|
||||
"\n" +
|
||||
"week_start\x18\x04 \x01(\tR\tweekStart\x12\x19\n" +
|
||||
"\bweek_end\x18\x05 \x01(\tR\aweekEnd\x12\x1f\n" +
|
||||
"\vcampus_code\x18\x06 \x01(\tR\n" +
|
||||
"campusCode\x12#\n" +
|
||||
"\rbuilding_code\x18\a \x01(\tR\fbuildingCode\"\x92\x01\n" +
|
||||
"\x18EmptyClassroomResultData\x12\x1a\n" +
|
||||
"\bsemester\x18\x01 \x01(\tR\bsemester\x12\x1d\n" +
|
||||
"\n" +
|
||||
"student_id\x18\x02 \x01(\tR\tstudentId\x12;\n" +
|
||||
"\n" +
|
||||
"classrooms\x18\x03 \x03(\v2\x1b.runner.EmptyClassroomEntryR\n" +
|
||||
"classrooms\"\x91\x01\n" +
|
||||
"\x13EmptyClassroomEntry\x12\x1c\n" +
|
||||
"\tclassroom\x18\x01 \x01(\tR\tclassroom\x12\x18\n" +
|
||||
"\aweekday\x18\x02 \x01(\tR\aweekday\x12\x18\n" +
|
||||
"\aperiods\x18\x03 \x01(\tR\aperiods\x12\x12\n" +
|
||||
"\x04term\x18\x04 \x01(\tR\x04term\x12\x14\n" +
|
||||
"\x05weeks\x18\x05 \x01(\tR\x05weeks*\xc5\x01\n" +
|
||||
"\bTaskType\x12\x15\n" +
|
||||
"\x11TASK_TYPE_UNKNOWN\x10\x00\x12\x13\n" +
|
||||
"\x0fTASK_TYPE_LOGIN\x10\x01\x12\x1a\n" +
|
||||
"\x16TASK_TYPE_GET_SCHEDULE\x10\x02\x12\x18\n" +
|
||||
"\x14TASK_TYPE_GET_GRADES\x10\x03\x12\x17\n" +
|
||||
"\x13TASK_TYPE_GET_EXAMS\x10\x04\x12\x1b\n" +
|
||||
"\x17TASK_TYPE_GET_USER_INFO\x10\x05*\x8a\x01\n" +
|
||||
"\x17TASK_TYPE_GET_USER_INFO\x10\x05\x12!\n" +
|
||||
"\x1dTASK_TYPE_GET_EMPTY_CLASSROOM\x10\x06*\x8a\x01\n" +
|
||||
"\n" +
|
||||
"TaskStatus\x12\x17\n" +
|
||||
"\x13TASK_STATUS_UNKNOWN\x10\x00\x12\x17\n" +
|
||||
@@ -1880,32 +2138,35 @@ func file_proto_runner_runner_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_proto_runner_runner_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||
var file_proto_runner_runner_proto_msgTypes = make([]protoimpl.MessageInfo, 22)
|
||||
var file_proto_runner_runner_proto_msgTypes = make([]protoimpl.MessageInfo, 25)
|
||||
var file_proto_runner_runner_proto_goTypes = []any{
|
||||
(TaskType)(0), // 0: runner.TaskType
|
||||
(TaskStatus)(0), // 1: runner.TaskStatus
|
||||
(*StreamMessage)(nil), // 2: runner.StreamMessage
|
||||
(*RegisterRequest)(nil), // 3: runner.RegisterRequest
|
||||
(*RegisterResponse)(nil), // 4: runner.RegisterResponse
|
||||
(*Heartbeat)(nil), // 5: runner.Heartbeat
|
||||
(*HeartbeatAck)(nil), // 6: runner.HeartbeatAck
|
||||
(*Task)(nil), // 7: runner.Task
|
||||
(*TaskCancel)(nil), // 8: runner.TaskCancel
|
||||
(*TaskResult)(nil), // 9: runner.TaskResult
|
||||
(*LoginPayload)(nil), // 10: runner.LoginPayload
|
||||
(*GetSchedulePayload)(nil), // 11: runner.GetSchedulePayload
|
||||
(*GetGradesPayload)(nil), // 12: runner.GetGradesPayload
|
||||
(*GetExamsPayload)(nil), // 13: runner.GetExamsPayload
|
||||
(*ScheduleResultData)(nil), // 14: runner.ScheduleResultData
|
||||
(*Course)(nil), // 15: runner.Course
|
||||
(*ScheduleTime)(nil), // 16: runner.ScheduleTime
|
||||
(*GpaSummary)(nil), // 17: runner.GpaSummary
|
||||
(*GradesResultData)(nil), // 18: runner.GradesResultData
|
||||
(*Grade)(nil), // 19: runner.Grade
|
||||
(*ExamsResultData)(nil), // 20: runner.ExamsResultData
|
||||
(*Exam)(nil), // 21: runner.Exam
|
||||
(*UserInfoResultData)(nil), // 22: runner.UserInfoResultData
|
||||
nil, // 23: runner.RegisterRequest.CapabilitiesEntry
|
||||
(TaskType)(0), // 0: runner.TaskType
|
||||
(TaskStatus)(0), // 1: runner.TaskStatus
|
||||
(*StreamMessage)(nil), // 2: runner.StreamMessage
|
||||
(*RegisterRequest)(nil), // 3: runner.RegisterRequest
|
||||
(*RegisterResponse)(nil), // 4: runner.RegisterResponse
|
||||
(*Heartbeat)(nil), // 5: runner.Heartbeat
|
||||
(*HeartbeatAck)(nil), // 6: runner.HeartbeatAck
|
||||
(*Task)(nil), // 7: runner.Task
|
||||
(*TaskCancel)(nil), // 8: runner.TaskCancel
|
||||
(*TaskResult)(nil), // 9: runner.TaskResult
|
||||
(*LoginPayload)(nil), // 10: runner.LoginPayload
|
||||
(*GetSchedulePayload)(nil), // 11: runner.GetSchedulePayload
|
||||
(*GetGradesPayload)(nil), // 12: runner.GetGradesPayload
|
||||
(*GetExamsPayload)(nil), // 13: runner.GetExamsPayload
|
||||
(*ScheduleResultData)(nil), // 14: runner.ScheduleResultData
|
||||
(*Course)(nil), // 15: runner.Course
|
||||
(*ScheduleTime)(nil), // 16: runner.ScheduleTime
|
||||
(*GpaSummary)(nil), // 17: runner.GpaSummary
|
||||
(*GradesResultData)(nil), // 18: runner.GradesResultData
|
||||
(*Grade)(nil), // 19: runner.Grade
|
||||
(*ExamsResultData)(nil), // 20: runner.ExamsResultData
|
||||
(*Exam)(nil), // 21: runner.Exam
|
||||
(*UserInfoResultData)(nil), // 22: runner.UserInfoResultData
|
||||
(*GetEmptyClassroomPayload)(nil), // 23: runner.GetEmptyClassroomPayload
|
||||
(*EmptyClassroomResultData)(nil), // 24: runner.EmptyClassroomResultData
|
||||
(*EmptyClassroomEntry)(nil), // 25: runner.EmptyClassroomEntry
|
||||
nil, // 26: runner.RegisterRequest.CapabilitiesEntry
|
||||
}
|
||||
var file_proto_runner_runner_proto_depIdxs = []int32{
|
||||
3, // 0: runner.StreamMessage.register:type_name -> runner.RegisterRequest
|
||||
@@ -1915,7 +2176,7 @@ var file_proto_runner_runner_proto_depIdxs = []int32{
|
||||
7, // 4: runner.StreamMessage.task:type_name -> runner.Task
|
||||
9, // 5: runner.StreamMessage.result:type_name -> runner.TaskResult
|
||||
8, // 6: runner.StreamMessage.task_cancel:type_name -> runner.TaskCancel
|
||||
23, // 7: runner.RegisterRequest.capabilities:type_name -> runner.RegisterRequest.CapabilitiesEntry
|
||||
26, // 7: runner.RegisterRequest.capabilities:type_name -> runner.RegisterRequest.CapabilitiesEntry
|
||||
0, // 8: runner.Task.type:type_name -> runner.TaskType
|
||||
1, // 9: runner.TaskResult.status:type_name -> runner.TaskStatus
|
||||
15, // 10: runner.ScheduleResultData.courses:type_name -> runner.Course
|
||||
@@ -1923,13 +2184,14 @@ var file_proto_runner_runner_proto_depIdxs = []int32{
|
||||
19, // 12: runner.GradesResultData.grades:type_name -> runner.Grade
|
||||
17, // 13: runner.GradesResultData.gpa_summary:type_name -> runner.GpaSummary
|
||||
21, // 14: runner.ExamsResultData.exams:type_name -> runner.Exam
|
||||
2, // 15: runner.RunnerHub.Connect:input_type -> runner.StreamMessage
|
||||
2, // 16: runner.RunnerHub.Connect:output_type -> runner.StreamMessage
|
||||
16, // [16:17] is the sub-list for method output_type
|
||||
15, // [15:16] is the sub-list for method input_type
|
||||
15, // [15:15] is the sub-list for extension type_name
|
||||
15, // [15:15] is the sub-list for extension extendee
|
||||
0, // [0:15] is the sub-list for field type_name
|
||||
25, // 15: runner.EmptyClassroomResultData.classrooms:type_name -> runner.EmptyClassroomEntry
|
||||
2, // 16: runner.RunnerHub.Connect:input_type -> runner.StreamMessage
|
||||
2, // 17: runner.RunnerHub.Connect:output_type -> runner.StreamMessage
|
||||
17, // [17:18] is the sub-list for method output_type
|
||||
16, // [16:17] is the sub-list for method input_type
|
||||
16, // [16:16] is the sub-list for extension type_name
|
||||
16, // [16:16] is the sub-list for extension extendee
|
||||
0, // [0:16] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_proto_runner_runner_proto_init() }
|
||||
@@ -1952,7 +2214,7 @@ func file_proto_runner_runner_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_runner_runner_proto_rawDesc), len(file_proto_runner_runner_proto_rawDesc)),
|
||||
NumEnums: 2,
|
||||
NumMessages: 22,
|
||||
NumMessages: 25,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
||||
@@ -81,6 +81,7 @@ enum TaskType {
|
||||
TASK_TYPE_GET_GRADES = 3; // 获取成绩
|
||||
TASK_TYPE_GET_EXAMS = 4; // 获取考试安排
|
||||
TASK_TYPE_GET_USER_INFO = 5; // 获取用户信息
|
||||
TASK_TYPE_GET_EMPTY_CLASSROOM = 6; // 获取空教室
|
||||
}
|
||||
|
||||
// TaskStatus 任务状态枚举
|
||||
@@ -236,3 +237,30 @@ message UserInfoResultData {
|
||||
string class_name = 6; // 班级
|
||||
string grade = 7; // 年级
|
||||
}
|
||||
|
||||
// GetEmptyClassroomPayload 获取空教室任务载荷
|
||||
message GetEmptyClassroomPayload {
|
||||
string username = 1; // 学号
|
||||
string password = 2; // 密码
|
||||
string semester = 3; // 学期
|
||||
string week_start = 4; // 起始周次
|
||||
string week_end = 5; // 结束周次
|
||||
string campus_code = 6; // 校区代码
|
||||
string building_code = 7; // 楼号代码(可选)
|
||||
}
|
||||
|
||||
// EmptyClassroomResultData 空教室结果数据
|
||||
message EmptyClassroomResultData {
|
||||
string semester = 1; // 学期
|
||||
string student_id = 2; // 学号
|
||||
repeated EmptyClassroomEntry classrooms = 3; // 空教室列表
|
||||
}
|
||||
|
||||
// EmptyClassroomEntry 空教室条目
|
||||
message EmptyClassroomEntry {
|
||||
string classroom = 1; // 教室名称
|
||||
string weekday = 2; // 星期几
|
||||
string periods = 3; // 可用节次
|
||||
string term = 4; // 学期描述
|
||||
string weeks = 5; // 周次范围
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.1
|
||||
// - protoc-gen-go-grpc v1.6.2
|
||||
// - protoc v7.34.1
|
||||
// source: proto/runner/runner.proto
|
||||
|
||||
|
||||
Reference in New Issue
Block a user