private async Task <Degree> ParseDegree(string url, string name) { var content = await HttpClient.GetHtmlAsync(url); var degreeText = content .QuerySelectorAll(".content > form > table tr") .SingleOrDefault(x => x.ChildNodes.Count == 1)?.InnerText; var match = degreeText == null ? null : Regex.Match(degreeText, @"\[(?<DegreeId>\d+)\] (?<DegreeName>[\w\s]+): \[(?<MajorId>\d+)\] (?<MajorName>[\w\s]+)"); var major = new Major { Id = match?.Groups["MajorId"]?.Value, Name = match?.Groups["MajorName"]?.Value, }; var degree = new Degree { Id = match?.Groups["DegreeId"]?.Value, Name = match?.Groups["DegreeName"]?.Value, GradingMajors = new [] { major } }; var examRows = content.QuerySelectorAll(".content > form > table:nth-child(5) tr").Where(row => row.Descendants("td").Count() == 9 && row.Descendants("td").FirstOrDefault()?.HasClass("qis_konto") == false && row.Descendants("td").FirstOrDefault()?.HasClass("qis_kontoOnTop") == false); var exams = examRows.Select(row => new ExamResult { Date = ParseColumnDateTime(row, 8), Grade = ParseColumnFloat(row, 3), Name = ParseColumnText(row, 1), Semester = Semester.Parse(ParseColumnText(row, 2)), Try = ParseColumnInt(row, 7), ExamNumber = ParseColumnText(row, 0), ExamState = ParseExamState(ParseColumnText(row, 4)) }); major.Gradings = exams.ToArray(); return(degree); }
private static void Main(string[] args) { string input = null; var baseUrl = ReadWithDefault("Please enter the base url of the LSF", "https://lsf.ovgu.de"); var semesterStr = ReadWithDefault("Please enter a semester", Semester.Current.Next().ToString()); var semester = Semester.Parse(semesterStr); var httpClient = new LsfHttpClientImpl(baseUrl); var client = new LsfScheduleClient(httpClient, new CalEventParser()); var builder = new ScheduleBuilder <CalSchedule, CalScheduleItem>(client, new GenericScheduleItemFactory <CalScheduleItem>((appointment, component) => new CalScheduleItem { Appointment = appointment, ScheduleComponent = component })); CalSchedule[] schedules = null; var earlyCriterion = new EarlyCriterion(); var equallyDistributedAppointmentsPerDayCriterion = new EquallyDistributedAppointmentsPerDayCriterion(); var freeDaysCriterion = new FreeDaysCriterion(); var eventsOnSameAlternatingSlotCriterion = new EventsOnSameAlternatingSlotCriterion(10000); var sameAlternatingWeekCriterion = new SameAlternatingWeekCriterion(); var noBreaksCriterion = new NoBreaksCriterion(); const string actionAddEvent = "0"; const string addCriterion = "1"; const string actionBuild = "2"; const string actionToIcal = "3"; const string actionToLsf = "4"; const string actionPrintCal = "5"; const string actionLoadFromFile = "6"; const string actionSaveFile = "7"; const string actionExit = "8"; const string actionGetLoginCookie = "9"; while (input != actionExit) { // Console.Clear(); Console.WriteLine( $"{builder.EventsCount} events saved. Timetables are {(builder.IsBuild ? "" : "not ")}build"); Console.WriteLine($"[{actionAddEvent}]: Add an event"); Console.WriteLine($"[{addCriterion}]: Configure a preferences"); Console.WriteLine($"[{actionBuild}]: Build timetables"); Console.WriteLine($"[{actionToIcal}]: Export timetable to ical"); Console.WriteLine($"[{actionToLsf}]: Export timetable to lsf"); Console.WriteLine($"[{actionPrintCal}]: Print timetable to console"); Console.WriteLine($"[{actionLoadFromFile}]: Load previous configured events from file"); Console.WriteLine($"[{actionSaveFile}]: Safe configured events to file"); Console.WriteLine($"[{actionExit}]: Exit"); input = Console.ReadLine(); switch (input) { case actionAddEvent: { Console.Write("Please paste the url of the event: "); input = Console.ReadLine(); var eventId = Regex.Match(input, @"publishid=(\d+)").Groups[1].Value; var e = builder.AddEvent(eventId); Console.Write( "Does the event consist of small groups and did you already got assigned (to one or more)? [y/N]: "); input = Console.ReadLine(); if (input.ToLower() == "y") { while (input.ToLower() == "y") { Console.Write("GroupName (usually the group number): "); var groupName = Console.ReadLine(); e.WithFixedSmallGroup(groupName); Console.Write("Another one? [y/N]: "); input = Console.ReadLine(); } break; } Console.Write("Do you prefer a teacher for this event? [y/N]: "); input = Console.ReadLine(); while (input.ToLower() == "y") { Console.Write("Name of the person as written in the LSF: "); input = Console.ReadLine(); var person = input; builder.AddItemCriterion(new TeacherCriterion(person, true, eventId)); Console.Write("Prefer another one as well? [y/N]: "); input = Console.ReadLine(); } Console.Write("Do you want to avoid a teacher? [y/N]: "); input = Console.ReadLine(); while (input.ToLower() == "y") { Console.Write("Name of the person as written in the LSF: "); input = Console.ReadLine(); var person = input; builder.AddItemCriterion(new TeacherCriterion(person, false, eventId)); Console.Write("Avoid another one as well? [y/N]: "); input = Console.ReadLine(); } break; } case addCriterion: { void AddOrRemoveCriterion(ICriterion criterion, string message) { var currentState = builder.HasCriterion(criterion); Console.Write($"{message} [{(currentState ? "Y/n" : "y/N")}]: "); input = Console.ReadLine().ToLower(); if (input == "y" && !currentState) { switch (criterion) { case IItemCriterion itemCriterion: builder.AddItemCriterion(itemCriterion); break; case IScheduleCriterion scheduleCriterion: builder.AddScheduleCriterion(scheduleCriterion); break; } } else if (input == "n" && currentState) { switch (criterion) { case IItemCriterion itemCriterion: builder.RemoveItemCriterion(itemCriterion); break; case IScheduleCriterion scheduleCriterion: builder.RemoveScheduleCriterion(scheduleCriterion); break; } } } AddOrRemoveCriterion(earlyCriterion, "Do you prefer an early timetable?"); AddOrRemoveCriterion(equallyDistributedAppointmentsPerDayCriterion, "Do you prefer appointments equally distributed over every day?"); AddOrRemoveCriterion(freeDaysCriterion, "Do you prefer free days?"); AddOrRemoveCriterion(eventsOnSameAlternatingSlotCriterion, "Do you prefer filling an empty 2-Weeks event slot with an other 2-Weeks event?"); AddOrRemoveCriterion(sameAlternatingWeekCriterion, "Do you prefer all 2-Week events to be on the same week?"); AddOrRemoveCriterion(noBreaksCriterion, "Do you prefer no breaks between you lessons?"); break; } case actionBuild: { schedules = builder.Build().Result; break; } case actionToIcal: { if (schedules is null) { Console.WriteLine("Please build the schedules first."); break; } Console.Write( "How many schedules (sorted by rating) to you want to export? Leaving this empty will export only the top rated one. [1-" + schedules.Length + "] "); input = Console.ReadLine(); var amount = 1; if (string.IsNullOrEmpty(input) || int.TryParse(input, out amount) && amount <= schedules.Length) { Console.Write("Please enter the output folder: "); var folder = Console.ReadLine(); for (var i = 0; i < amount; i++) { var schedule = schedules[i]; var serializer = new CalendarSerializer(schedule.ToCalendar()); var ical = serializer.SerializeToString(); File.WriteAllText(Path.Combine(folder, "schedule-" + i + ".ical"), ical); } } else { Console.WriteLine("Invalid input"); } break; } case actionToLsf: { if (schedules is null) { Console.WriteLine("Please build the schedules first."); break; } if (schedules.Length == 0) { Console.WriteLine("There are no schedules to export."); break; } Console.Write("Which schedule? (Defaults to 1) [1-" + schedules.Length + "] "); input = Console.ReadLine(); var i = 1; if (string.IsNullOrEmpty(input) || int.TryParse(input, out i) && i <= schedules.Length) { if (!httpClient.IsAuthenticated) { if (!Authenticate(httpClient)) { break; } } var top = schedules[i]; httpClient.SetSemester(semester).Wait(); client.ReplaceSchedule(top.ScheduleItems.Select(x => x.ScheduleComponent).ToArray()) .Wait(); Console.WriteLine("Saved best schedule to lsf account!"); } else { Console.WriteLine("Invalid input"); } break; } case actionPrintCal: { Console.Write("Which schedule? (Defaults to 1) [1-" + schedules.Length + "] "); input = Console.ReadLine(); var i = 1; if (string.IsNullOrEmpty(input) || int.TryParse(input, out i) && i <= schedules.Length) { var top = schedules[i]; Console.WriteLine(new CalendarPrinter(Console.BufferWidth, FormattingStyle.Console, top.ToCalendar()).Print()); } else { Console.WriteLine("Invalid input"); } break; } case actionSaveFile: { var path = Path.GetFullPath(Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "lsf-api", "events.json")); Console.Write($"Where should the file be stored? [{path}]: "); input = Console.ReadLine(); if (!string.IsNullOrEmpty(input)) { path = Path.GetFullPath(input); } Directory.CreateDirectory(Path.GetDirectoryName(path)); var state = builder.GetStateSnapshot(); File.WriteAllText(path, state); break; } case actionLoadFromFile: { var path = Path.GetFullPath(Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "lsf-api", "events.json")); Console.Write($"Where should is file stored? [{path}]: "); input = Console.ReadLine(); if (!string.IsNullOrEmpty(input)) { path = Path.GetFullPath(input); } if (!File.Exists(path)) { Console.WriteLine("The file does not exist."); break; } var state = File.ReadAllText(path); builder.LoadStateSnapshot(state); break; } case actionGetLoginCookie: { if (!Authenticate(httpClient)) { break; } var cookie = httpClient.GetLoginCookie(); Console.WriteLine( "Here is your session cookie. Treat this as a password as anybody can access your account using this cookie until you log out of the session."); Console.WriteLine(cookie); break; } default: { Console.WriteLine("Unrecognized input"); break; } } } }