package fetch import ( "fmt" "github.com/labstack/echo/v5" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/apis" "golang.org/x/net/html" "htwk-planner/model" "htwk-planner/service/date" "htwk-planner/service/db" "io" "net/http" "regexp" "strconv" "strings" "time" ) func GetSeminarEvents(c echo.Context, app *pocketbase.PocketBase) error { seminarGroupsLabel := db.GetAllCourses(app) seminarGroups := GetSeminarGroupsEventsFromHTML(seminarGroupsLabel) collection, dbError := db.FindCollection(app, "events") if dbError != nil { return apis.NewNotFoundError("Collection not found", dbError) } seminarGroups = clearEmptySeminarGroups(seminarGroups) savedRecords, dbError := db.SaveEvents(seminarGroups, collection, app) if dbError != nil { return apis.NewNotFoundError("Events could not be saved", dbError) } return c.JSON(http.StatusOK, savedRecords) } func clearEmptySeminarGroups(seminarGroups []model.SeminarGroup) []model.SeminarGroup { var newSeminarGroups []model.SeminarGroup for _, seminarGroup := range seminarGroups { if len(seminarGroup.Events) > 0 && seminarGroup.Course != "" { newSeminarGroups = append(newSeminarGroups, seminarGroup) } } return newSeminarGroups } func GetSeminarGroupsEventsFromHTML(seminarGroupsLabel []string) []model.SeminarGroup { var seminarGroups []model.SeminarGroup for _, seminarGroupLabel := range seminarGroupsLabel { result, getError := getPlanHTML("ss", seminarGroupLabel) if getError == nil { seminarGroup := parseSeminarGroup(result) seminarGroups = append(seminarGroups, seminarGroup) } result, getError = getPlanHTML("ws", seminarGroupLabel) if getError == nil { seminarGroup := parseSeminarGroup(result) seminarGroups = append(seminarGroups, seminarGroup) } } return seminarGroups } func parseSeminarGroup(result string) model.SeminarGroup { doc, err := html.Parse(strings.NewReader(result)) if err != nil { fmt.Printf("Error occurred while parsing the HTML document: %s\n", err.Error()) return model.SeminarGroup{} } table := findFirstTable(doc) eventTables := getEventTables(doc) allDayLabels := getAllDayLabels(doc) if eventTables == nil || allDayLabels == nil { return model.SeminarGroup{} } eventsWithCombinedWeeks := toEvents(eventTables, allDayLabels) splitEventsByWeekVal := splitEventsByWeek(eventsWithCombinedWeeks) events := splitEventsBySingleWeek(splitEventsByWeekVal) semesterString := findFirstSpanWithClass(table, "header-0-2-0").FirstChild.Data semester, year := extractSemesterAndYear(semesterString) events = convertWeeksToDates(events, semester, year) var seminarGroup = model.SeminarGroup{ University: findFirstSpanWithClass(table, "header-1-0-0").FirstChild.Data, Course: findFirstSpanWithClass(table, "header-2-0-1").FirstChild.Data, Events: events, } return seminarGroup } func convertWeeksToDates(events []model.Event, semester string, year string) []model.Event { var newEvents []model.Event eventYear, _ := strconv.Atoi(year) // for each event we need to calculate the start and end date based on the week and the year for _, event := range events { eventWeek, _ := strconv.Atoi(event.Week) eventDay, _ := date.GetDateFromWeekNumber(eventYear, eventWeek, event.Day) start := addTimeToDate(eventDay, event.Start) end := addTimeToDate(eventDay, event.End) newEvent := event newEvent.Start = start.String() newEvent.End = end.String() newEvent.Semester = semester newEvents = append(newEvents, newEvent) } return newEvents } func addTimeToDate(date time.Time, timeString string) time.Time { //convert time string to time timeParts := strings.Split(timeString, ":") hour, _ := strconv.Atoi(timeParts[0]) minute, _ := strconv.Atoi(timeParts[1]) return time.Date(date.Year(), date.Month(), date.Day(), hour, minute, 0, 0, time.UTC) } func extractSemesterAndYear(semesterString string) (string, string) { winterPattern := "Wintersemester" summerPattern := "Sommersemester" winterMatch := strings.Contains(semesterString, winterPattern) summerMatch := strings.Contains(semesterString, summerPattern) semester := "" semesterShortcut := "" if winterMatch { semester = "Wintersemester" semesterShortcut = "ws" } else if summerMatch { semester = "Sommersemester" semesterShortcut = "ss" } else { return "", "" } yearPattern := `\d{4}` combinedPattern := semester + `\s` + yearPattern re := regexp.MustCompile(combinedPattern) match := re.FindString(semesterString) year := "" if match != "" { reYear := regexp.MustCompile(yearPattern) year = reYear.FindString(match) } return semesterShortcut, year } func toEvents(tables [][]*html.Node, days []string) []model.Event { var events []model.Event for table := range tables { for row := range tables[table] { tableData := findTableData(tables[table][row]) if len(tableData) > 0 { events = append(events, model.Event{ Day: days[table], Week: getTextContent(tableData[0]), Start: getTextContent(tableData[1]), End: getTextContent(tableData[2]), Name: getTextContent(tableData[3]), EventType: getTextContent(tableData[4]), Prof: getTextContent(tableData[5]), Rooms: getTextContent(tableData[6]), Notes: getTextContent(tableData[7]), BookedAt: getTextContent(tableData[8]), }) } } } return events } func splitEventsByWeek(events []model.Event) []model.Event { var newEvents []model.Event for _, event := range events { weeks := strings.Split(event.Week, ",") for _, week := range weeks { newEvent := event newEvent.Week = strings.TrimSpace(week) newEvents = append(newEvents, newEvent) } } return newEvents } func splitEventsBySingleWeek(events []model.Event) []model.Event { var newEvents []model.Event for _, event := range events { if strings.Contains(event.Week, "-") { weeks := splitWeekRange(event.Week) for _, week := range weeks { newEvent := event newEvent.Week = week newEvents = append(newEvents, newEvent) } } else { newEvents = append(newEvents, event) } } return newEvents } func splitWeekRange(weekRange string) []string { parts := strings.Split(weekRange, "-") if len(parts) != 2 { return nil // Invalid format } start, errStart := strconv.Atoi(strings.TrimSpace(parts[0])) end, errEnd := strconv.Atoi(strings.TrimSpace(parts[1])) if errStart != nil || errEnd != nil { return nil // Error converting to integers } var weeks []string for i := start; i <= end; i++ { weeks = append(weeks, strconv.Itoa(i)) } return weeks } func toUtf8(iso88591Buf []byte) string { buf := make([]rune, len(iso88591Buf)) for i, b := range iso88591Buf { buf[i] = rune(b) } return string(buf) } // getPlanHTML Get the HTML document from the specified URL func getPlanHTML(semester string, matrikel string) (string, error) { url := "https://stundenplan.htwk-leipzig.de/" + string(semester) + "/Berichte/Text-Listen;Studenten-Sets;name;" + matrikel + "?template=sws_semgrp&weeks=1-65" // Send GET request response, err := http.Get(url) if err != nil { fmt.Printf("Error occurred while making the request: %s\n", err.Error()) return "", err } defer func(Body io.ReadCloser) { err := Body.Close() if err != nil { return } }(response.Body) // Read the response body body, err := io.ReadAll(response.Body) if err != nil { fmt.Printf("Error occurred while reading the response: %s\n", err.Error()) return "", err } return toUtf8(body), err }