diff --git a/api.go b/api.go new file mode 100644 index 0000000..dcb367e --- /dev/null +++ b/api.go @@ -0,0 +1,112 @@ +package banner + +import ( + "encoding/json" + "log" + "net/http" + "strconv" + "strings" + "time" +) + +func getCourseMeetingTime(term int, crn int) MeetingTimeResponse { + url := buildURL("searchResults/getFacultyMeetingTimes", map[string]string{ + "term": strconv.Itoa(term), + "courseReferenceNumber": strconv.Itoa(crn), + }) + + // Build the request + log.Printf("GET %s", url) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + log.Fatal(err) + } + AddUserAgent(req) + + resp, err := client.Do(req) + if err != nil { + log.Fatal(err) + } + + if !ContainsContentType(resp.Header.Get("Content-Type"), "application/json") { + log.Fatalf("Response was not JSON: %s", resp.Header.Get("Content-Type")) + } + + defer resp.Body.Close() + var body map[string][]map[string]interface{} + err = json.NewDecoder(resp.Body).Decode(&body) + if err != nil { + log.Fatal(err) + } + + if len(body["fmt"]) > 1 { + log.Printf("Expected only one fmt child, found %d", len(body["fmt"])) + } + + // Acquire & cast the meeting time data + meetingTimeMap := body["fmt"][0]["meetingTime"].(map[string]interface{}) + + // Extract weekdays + weekdays := make(map[time.Weekday]bool) + for i := int(time.Sunday); i <= int(time.Saturday); i++ { + day := time.Weekday(i) + dayActive := meetingTimeMap[strings.ToLower(day.String())].(bool) + weekdays[day] = dayActive + } + meetingScheduleType := meetingTimeMap["meetingScheduleType"].(string) + meetingType := meetingTimeMap["meetingType"].(string) + meetingTypeDescription := meetingTimeMap["meetingTypeDescription"].(string) + + // Extract other data + campus := meetingTimeMap["campus"].(string) + campusDescription := meetingTimeMap["campusDescription"].(string) + building := meetingTimeMap["building"].(string) + buildingDescription := meetingTimeMap["buildingDescription"].(string) + room := "N/A" + if meetingTimeMap["room"] != nil { + room = meetingTimeMap["room"].(string) + } + + creditHours := meetingTimeMap["creditHourSession"].(float64) + hoursPerWeek := meetingTimeMap["hoursWeek"].(float64) + + // Parse dates & times + const layout = "01/02/2006" + dateStart, _ := time.Parse(layout, meetingTimeMap["startDate"].(string)) + dateEnd, _ := time.Parse(layout, meetingTimeMap["endDate"].(string)) + timeStart, _ := strconv.ParseUint(meetingTimeMap["beginTime"].(string), 10, 0) + timeEnd, _ := strconv.ParseUint(meetingTimeMap["endTime"].(string), 10, 0) + + // Extract faculty data + faculty := make([]MeetingTimeFaculty, 0) + for _, facultyMap := range body["fmt"][0]["faculty"].([]interface{}) { + facultyMap := facultyMap.(map[string]interface{}) + bannerId, _ := strconv.Atoi(facultyMap["bannerId"].(string)) + faculty = append(faculty, MeetingTimeFaculty{ + bannerId: bannerId, + category: facultyMap["category"].(string), + displayName: facultyMap["displayName"].(string), + email: facultyMap["emailAddress"].(string), + primary: facultyMap["primaryIndicator"].(bool), + }) + } + + return MeetingTimeResponse{ + faculty: faculty, + weekdays: weekdays, + campus: campus, + campusDescription: campusDescription, + creditHours: int(creditHours), + building: building, + buildingDescription: buildingDescription, + room: room, + timeStart: timeStart, + timeEnd: timeEnd, + dateStart: dateStart, + dateEnd: dateEnd, + hoursPerWeek: float32(hoursPerWeek), + meetingScheduleType: meetingScheduleType, + meetingType: meetingType, + meetingTypeDescription: meetingTypeDescription, + } +} diff --git a/helpers.go b/helpers.go new file mode 100644 index 0000000..14cb719 --- /dev/null +++ b/helpers.go @@ -0,0 +1,40 @@ +package banner + +import ( + "net/http" + "strings" +) + +func buildURL(path string, params map[string]string) string { + // Builds a URL for the given path and parameters + url := baseURL + path + + if params != nil { + takenFirst := false + for key, value := range params { + paramChar := "&" + if !takenFirst { + paramChar = "?" + takenFirst = true + } + + url += paramChar + key + "=" + value + } + } + + return url +} + +func AddUserAgent(req *http.Request) { + req.Header.Add("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36") +} + +func ContainsContentType(header string, search string) bool { + // Split on commas, check if any of the types match + for _, content_type := range strings.Split(header, ";") { + if content_type == search { + return true + } + } + return false +} diff --git a/main.go b/main.go index 8663dc2..af22fbf 100644 --- a/main.go +++ b/main.go @@ -1,14 +1,10 @@ -package main +package banner import ( - "encoding/json" "log" "net/http" "net/http/cookiejar" - "net/url" "os" - "strconv" - "strings" "time" "github.com/joho/godotenv" @@ -48,185 +44,6 @@ type MeetingTimeResponse struct { meetingTypeDescription string } -func setupSession() { - // Makes the initial requests that sets up the session cookies for the rest of the application - log.Println("Setting up session...") - - request_queue := []string{ - "/registration/registration", - "/selfServiceMenu/data", - } - - for _, path := range request_queue { - req, _ := http.NewRequest("GET", buildURL(path, nil), nil) - AddUserAgent(req) - res, _ := client.Do(req) - log.Println(res) - } - - // Validate that cookies were set - baseURL_parsed, _ := url.Parse(baseURL) - current_cookies := cookies.Cookies(baseURL_parsed) - required_cookies := map[string]bool{ - "JSESSIONID": false, - "SSB_COOKIE": false, - } - - for _, cookie := range current_cookies { - _, present := required_cookies[cookie.Name] - // Check if this cookie is required - if present { - required_cookies[cookie.Name] = true - } - } - - // Check if all required cookies were set - for cookie_name, cookie_set := range required_cookies { - if !cookie_set { - log.Fatalf("Required cookie %s was not set", cookie_name) - } - } - log.Println("All cookies acquired. Session setup complete.") - - // Validate that the session allows access to termSelection -} - -func buildURL(path string, params map[string]string) string { - // Builds a URL for the given path and parameters - url := baseURL + path - - if params != nil { - takenFirst := false - for key, value := range params { - paramChar := "&" - if !takenFirst { - paramChar = "?" - takenFirst = true - } - - url += paramChar + key + "=" + value - } - } - - return url -} - -func AddUserAgent(req *http.Request) { - req.Header.Add("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36") -} - -func ContainsContentType(header string, search string) bool { - // Split on commas, check if any of the types match - for _, content_type := range strings.Split(header, ";") { - if content_type == search { - return true - } - } - return false -} - -func getCourseMeetingTime(term int, crn int) MeetingTimeResponse { - url := buildURL("searchResults/getFacultyMeetingTimes", map[string]string{ - "term": strconv.Itoa(term), - "courseReferenceNumber": strconv.Itoa(crn), - }) - - // Build the request - log.Printf("GET %s", url) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - log.Fatal(err) - } - AddUserAgent(req) - - resp, err := client.Do(req) - if err != nil { - log.Fatal(err) - } - - if !ContainsContentType(resp.Header.Get("Content-Type"), "application/json") { - log.Fatalf("Response was not JSON: %s", resp.Header.Get("Content-Type")) - } - - defer resp.Body.Close() - var body map[string][]map[string]interface{} - err = json.NewDecoder(resp.Body).Decode(&body) - if err != nil { - log.Fatal(err) - } - - if len(body["fmt"]) > 1 { - log.Printf("Expected only one fmt child, found %d", len(body["fmt"])) - } - - // Acquire & cast the meeting time data - meetingTimeMap := body["fmt"][0]["meetingTime"].(map[string]interface{}) - - // Extract weekdays - weekdays := make(map[time.Weekday]bool) - for i := int(time.Sunday); i <= int(time.Saturday); i++ { - day := time.Weekday(i) - dayActive := meetingTimeMap[strings.ToLower(day.String())].(bool) - weekdays[day] = dayActive - } - meetingScheduleType := meetingTimeMap["meetingScheduleType"].(string) - meetingType := meetingTimeMap["meetingType"].(string) - meetingTypeDescription := meetingTimeMap["meetingTypeDescription"].(string) - - // Extract other data - campus := meetingTimeMap["campus"].(string) - campusDescription := meetingTimeMap["campusDescription"].(string) - building := meetingTimeMap["building"].(string) - buildingDescription := meetingTimeMap["buildingDescription"].(string) - room := "N/A" - if meetingTimeMap["room"] != nil { - room = meetingTimeMap["room"].(string) - } - - creditHours := meetingTimeMap["creditHourSession"].(float64) - hoursPerWeek := meetingTimeMap["hoursWeek"].(float64) - - // Parse dates & times - const layout = "01/02/2006" - dateStart, _ := time.Parse(layout, meetingTimeMap["startDate"].(string)) - dateEnd, _ := time.Parse(layout, meetingTimeMap["endDate"].(string)) - timeStart, _ := strconv.ParseUint(meetingTimeMap["beginTime"].(string), 10, 0) - timeEnd, _ := strconv.ParseUint(meetingTimeMap["endTime"].(string), 10, 0) - - // Extract faculty data - faculty := make([]MeetingTimeFaculty, 0) - for _, facultyMap := range body["fmt"][0]["faculty"].([]interface{}) { - facultyMap := facultyMap.(map[string]interface{}) - bannerId, _ := strconv.Atoi(facultyMap["bannerId"].(string)) - faculty = append(faculty, MeetingTimeFaculty{ - bannerId: bannerId, - category: facultyMap["category"].(string), - displayName: facultyMap["displayName"].(string), - email: facultyMap["emailAddress"].(string), - primary: facultyMap["primaryIndicator"].(bool), - }) - } - - return MeetingTimeResponse{ - faculty: faculty, - weekdays: weekdays, - campus: campus, - campusDescription: campusDescription, - creditHours: int(creditHours), - building: building, - buildingDescription: buildingDescription, - room: room, - timeStart: timeStart, - timeEnd: timeEnd, - dateStart: dateStart, - dateEnd: dateEnd, - hoursPerWeek: float32(hoursPerWeek), - meetingScheduleType: meetingScheduleType, - meetingType: meetingType, - meetingTypeDescription: meetingTypeDescription, - } -} - func main() { err := godotenv.Load() if err != nil { @@ -243,7 +60,7 @@ func main() { Jar: cookies, } - setupSession() + setup() meetingTime := getCourseMeetingTime(202420, 44142) log.Println(meetingTime) diff --git a/session.go b/session.go new file mode 100644 index 0000000..94f99fd --- /dev/null +++ b/session.go @@ -0,0 +1,50 @@ +package banner + +import ( + "log" + "net/http" + "net/url" +) + +func setup() { + // Makes the initial requests that sets up the session cookies for the rest of the application + log.Println("Setting up session...") + + request_queue := []string{ + "/registration/registration", + "/selfServiceMenu/data", + } + + for _, path := range request_queue { + req, _ := http.NewRequest("GET", buildURL(path, nil), nil) + AddUserAgent(req) + res, _ := client.Do(req) + log.Println(res) + } + + // Validate that cookies were set + baseURL_parsed, _ := url.Parse(baseURL) + current_cookies := cookies.Cookies(baseURL_parsed) + required_cookies := map[string]bool{ + "JSESSIONID": false, + "SSB_COOKIE": false, + } + + for _, cookie := range current_cookies { + _, present := required_cookies[cookie.Name] + // Check if this cookie is required + if present { + required_cookies[cookie.Name] = true + } + } + + // Check if all required cookies were set + for cookie_name, cookie_set := range required_cookies { + if !cookie_set { + log.Fatalf("Required cookie %s was not set", cookie_name) + } + } + log.Println("All cookies acquired. Session setup complete.") + + // Validate that the session allows access to termSelection +}