From 43bb9a6f4f8434f86b5d995a5f4cddf7f5cc85c3 Mon Sep 17 00:00:00 2001 From: Michael Gordeev Date: Tue, 11 Feb 2020 17:34:16 +0300 Subject: [PATCH] Redone parser, Fixed master schedule parsing and professors parsing issues --- .../Activities/ExportActivity.cs | 32 +- .../Activities/MainActivity.cs | 131 +++---- .../Activities/StartActivity.cs | 6 +- GUT.Schedule/GUTSchedule.Droid/Calendar.cs | 58 +-- .../GUTSchedule.Droid.csproj | 3 - GUT.Schedule/GUTSchedule/Extensions.cs | 16 - .../Models/DefaultExportParameters.cs | 1 - GUT.Schedule/GUTSchedule/Parser.cs | 355 +++++++++++++----- 8 files changed, 321 insertions(+), 281 deletions(-) diff --git a/GUT.Schedule/GUTSchedule.Droid/Activities/ExportActivity.cs b/GUT.Schedule/GUTSchedule.Droid/Activities/ExportActivity.cs index 857efc7..cfd3efd 100644 --- a/GUT.Schedule/GUTSchedule.Droid/Activities/ExportActivity.cs +++ b/GUT.Schedule/GUTSchedule.Droid/Activities/ExportActivity.cs @@ -31,36 +31,12 @@ namespace GUTSchedule.Droid.Activities { try { - if (Data.DataSet.IsProfessor == true) - status.Text = Resources.GetText(Resource.String.potatoLoadingStatus); // For some reason professors' schedule loads much slower - else - status.Text = Resources.GetText(Resource.String.loadingStatus); + status.Text = Resources.GetText(Resource.String.loadingStatus); - if (Data.DataSet.HttpClient != null) - { - List schedule = new List(); + List schedule = await Parser.GetSchedule(MainActivity.ExportParameters); - for (DateTime d = Data.StartDate; d <= Data.EndDate; d = d.AddMonths(1)) - schedule.AddRange(await Parser.GetCabinetSchedule(Data.DataSet.HttpClient, d, false)); // Even though the user can be professor he can be also PhD student (and have his student schedule) - - if (Data.DataSet.IsProfessor == true) - for (DateTime d = Data.StartDate; d <= Data.EndDate; d = d.AddMonths(1)) - schedule.AddRange(await Parser.GetCabinetSchedule(Data.DataSet.HttpClient, d, true)); - - schedule = schedule.FindAll(i => i.StartTime.Date >= Data.StartDate && i.StartTime.Date <= Data.EndDate); // Filtering schedule according to export range - - status.Text = Resources.GetText(Resource.String.calendarExportStatus); - Calendar.Export(schedule); - } - else - { - List schedule = await Parser.LoadSchedule(); - - schedule = schedule.FindAll(i => i.StartTime.Date >= Data.StartDate && i.StartTime.Date <= Data.EndDate); // Filtering schedule according to export range - - status.Text = Resources.GetText(Resource.String.calendarExportStatus); - Calendar.Export(schedule); - } + status.Text = Resources.GetText(Resource.String.calendarExportStatus); + Calendar.Export(schedule); status.Text = Resources.GetText(Resource.String.doneStatus); await Task.Delay(1000); diff --git a/GUT.Schedule/GUTSchedule.Droid/Activities/MainActivity.cs b/GUT.Schedule/GUTSchedule.Droid/Activities/MainActivity.cs index e45e878..56441c7 100644 --- a/GUT.Schedule/GUTSchedule.Droid/Activities/MainActivity.cs +++ b/GUT.Schedule/GUTSchedule.Droid/Activities/MainActivity.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using System.Net.Http; using Android.App; using Android.Content; using Android.Content.PM; @@ -10,17 +9,26 @@ using Android.Support.V7.App; using Android.Text.Method; using Android.Views; using Android.Widget; -using AngleSharp.Html.Dom; -using AngleSharp.Html.Parser; using GUTSchedule.Models; -using GUTSchedule; using GUTSchedule.Droid.Fragments; +using System.Collections.Generic; +using System.Threading.Tasks; namespace GUTSchedule.Droid.Activities { [Activity] public class MainActivity : AppCompatActivity { + public static ExportParameters ExportParameters { get; set; } + public static List<(string id, string name)> Faculties { get; set; } + public static List<(string id, string name)> Groups { get; set; } + public static bool AddGroupToTitle { get; set; } + public static int SelectedCalendarIndex { get; set; } + public static int Reminder { get; set; } + + DateTime startDate = DateTime.Today; + DateTime endDate = DateTime.Today.AddDays(7); + Button start, end, export; Button forDay, forWeek, forMonth, forSemester; Spinner faculty, course, group, reminder, calendar; @@ -31,7 +39,7 @@ namespace GUTSchedule.Droid.Activities ISharedPreferences prefs; - protected override void OnCreate(Bundle savedInstanceState) + protected override async void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); SetContentView(Resource.Layout.Main); @@ -42,14 +50,17 @@ namespace GUTSchedule.Droid.Activities AssignVariables(); - faculty.SetList(this, Data.Faculties.Select(i => i.Name)); - int s = Data.Faculties.FindIndex(i => i.Id == prefs.GetString("Faculty", "-123")); + faculty.SetList(this, Faculties.Select(i => i.name)); + int s = Faculties.FindIndex(i => i.id == prefs.GetString("Faculty", "-123")); faculty.SetSelection(s == -1 ? 0 : s); course.SetList(this, "1234".ToCharArray()); course.SetSelection(prefs.GetInt("Course", 0)); // IDK why but this shit triggers events anyway (even if they are set in the next line. It seem to be that there's some asynchronous shit somewhere there) // P.S. Fuck Android + await Task.Delay(100); + UpdateGroupsList(); + AddEvents(); // Settings spinners' dropdown lists content @@ -66,8 +77,8 @@ namespace GUTSchedule.Droid.Activities s = Calendar.Calendars.FindIndex(i => i.Id == prefs.GetString("Calendar", "-123")); calendar.SetSelection(s == -1 ? 0 : s); - end.Text = Data.EndDate.ToShortDateString(); - start.Text = Data.StartDate.ToShortDateString(); + end.Text = endDate.ToShortDateString(); + start.Text = startDate.ToShortDateString(); groupTitle.Checked = prefs.GetBoolean("AddGroupToHeader", false); authorize.Checked = prefs.GetBoolean("Authorize", true); @@ -76,22 +87,19 @@ namespace GUTSchedule.Droid.Activities password.Text = prefs.GetString("password", ""); } - private async void Export_Click(object sender, EventArgs e) + private void Export_Click(object sender, EventArgs e) { error.Visibility = ViewStates.Gone; - if (Data.StartDate > Data.EndDate) + if (startDate > endDate) { error.Text = Resources.GetText(Resource.String.invalidDateRangeError); error.Visibility = ViewStates.Visible; return; } - HttpClient client = null; - bool? isProf = null; if (authorize.Checked) { - Toast.MakeText(ApplicationContext, Resources.GetText(Resource.String.authorizationState), ToastLength.Short).Show(); if (string.IsNullOrWhiteSpace(email.Text) || string.IsNullOrWhiteSpace(password.Text)) { error.Text = Resources.GetText(Resource.String.invalidAuthorizationError); @@ -99,44 +107,13 @@ namespace GUTSchedule.Droid.Activities return; } - export.Enabled = false; - client = new HttpClient(); - - await client.GetAsync("https://cabs.itut.ru/cabinet/"); - - using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "https://cabs.itut.ru/cabinet/lib/autentificationok.php"); - request.SetContent( - ("users", email.Text), - ("parole", password.Text)); - - HttpResponseMessage response = await client.SendAsync(request); - string responseContent = await response.GetString(); - export.Enabled = true; - - if (!response.IsSuccessStatusCode) + ExportParameters = new CabinetExportParameters { - error.Text = $"{Resources.GetText(Resource.String.authorizationError)}: {response.StatusCode}: {responseContent}"; - error.Visibility = ViewStates.Visible; - return; - } - - if (!responseContent.StartsWith("1", StringComparison.OrdinalIgnoreCase)) - { - error.Text = $"{Resources.GetText(Resource.String.invalidCredentialError)} ({string.Join("; ", responseContent.Replace("error=", "", StringComparison.OrdinalIgnoreCase).Split('|'))})"; - error.Visibility = ViewStates.Visible; - return; - } - - export.Enabled = false; - HttpResponseMessage verificationResponse = await client.GetAsync("https://cabs.itut.ru/cabinet/?login=yes"); - export.Enabled = true; - IHtmlDocument doc = new HtmlParser().ParseDocument(await verificationResponse.GetString()); - if (doc.QuerySelectorAll("option").Any(i => i.TextContent.Contains("Сотрудник"))) - isProf = true; - else - isProf = false; - - Data.Groups = null; + Email = email.Text, + Password = password.Text, + EndDate = endDate, + StartDate = startDate + }; // Если ты это читаешь и у тебя возникли вопросы по типу "А какого хуя творится в коде ниже?!", то во-первых: // According to this SO thread: https://stackoverflow.com/questions/1925486/android-storing-username-and-password @@ -150,40 +127,40 @@ namespace GUTSchedule.Droid.Activities } else { - if (Data.Groups.Count < 1) + if (Groups.Count < 1) { error.Text = Resources.GetText(Resource.String.groupSelectionError); error.Visibility = ViewStates.Visible; return; } + + ExportParameters = new DefaultExportParameters + { + EndDate = endDate, + StartDate = startDate, + Course = (course.SelectedItemPosition + 1).ToString(), + FacultyId = Faculties[faculty.SelectedItemPosition].id, + GroupId = Groups[group.SelectedItemPosition].id, + }; } - // Forming export parameters - Data.DataSet = new DataSet - { - Faculty = Data.Faculties[faculty.SelectedItemPosition].Id, - Group = Data.Groups?[group.SelectedItemPosition].Id, - Course = course.SelectedItemPosition + 1, - AddGroupToTitle = groupTitle.Checked, - Calendar = Calendar.Calendars[calendar.SelectedItemPosition].Id, - Reminder = (reminder.SelectedItemPosition - 1) * 5, - HttpClient = client, - IsProfessor = isProf - }; + AddGroupToTitle = groupTitle.Checked; + SelectedCalendarIndex = calendar.SelectedItemPosition; + Reminder = (reminder.SelectedItemPosition - 1) * 5; StartActivity(new Intent(this, typeof(ExportActivity))); } private async void End_Click(object sender, EventArgs e) { - Data.EndDate = await new DatePickerFragment().GetDate(SupportFragmentManager, Data.EndDate); - end.Text = Data.EndDate.ToShortDateString(); + endDate = await new DatePickerFragment().GetDate(SupportFragmentManager, endDate); + end.Text = endDate.ToShortDateString(); } private async void Start_Click(object sender, EventArgs e) { - Data.StartDate = await new DatePickerFragment().GetDate(SupportFragmentManager, Data.StartDate); - start.Text = Data.StartDate.ToShortDateString(); + startDate = await new DatePickerFragment().GetDate(SupportFragmentManager, startDate); + start.Text = startDate.ToShortDateString(); } private async void UpdateGroupsList() @@ -191,17 +168,17 @@ namespace GUTSchedule.Droid.Activities if (course.SelectedItem == null) return; - await Parser.LoadGroups(Data.Faculties[faculty.SelectedItemPosition].Id, course.SelectedItemPosition + 1); - group.SetList(this, Data.Groups.Select(i => i.Name)); + Groups = await Parser.GetGroups(Faculties[faculty.SelectedItemPosition].id, (course.SelectedItemPosition + 1).ToString()); + group.SetList(this, Groups.Select(i => i.name)); - int s = Data.Groups?.FindIndex(i => i.Id == prefs.GetString("Group", "-123")) ?? 0; + int s = Groups?.FindIndex(i => i.id == prefs.GetString("Group", "-123")) ?? 0; group.SetSelection(s == -1 ? 0 : s); } private void SetDate(int days) { - Data.EndDate = Data.StartDate.AddDays(days); - end.Text = Data.EndDate.ToShortDateString(); + endDate = startDate.AddDays(days); + end.Text = endDate.ToShortDateString(); } #region Init stuff @@ -238,7 +215,7 @@ namespace GUTSchedule.Droid.Activities { faculty.ItemSelected += (s, e) => { - prefs.Edit().PutString("Faculty", Data.Faculties[e.Position].Id).Apply(); + prefs.Edit().PutString("Faculty", Faculties[e.Position].id).Apply(); UpdateGroupsList(); }; course.ItemSelected += (s, e) => @@ -265,7 +242,7 @@ namespace GUTSchedule.Droid.Activities reminder.ItemSelected += (s, e) => prefs.Edit().PutInt("Reminder", e.Position).Apply(); group.ItemSelected += (s, e) => - prefs.Edit().PutString("Group", Data.Groups[e.Position].Id).Apply(); + prefs.Edit().PutString("Group", Groups[e.Position].id).Apply(); groupTitle.Click += (s, e) => prefs.Edit().PutBoolean("AddGroupToHeader", groupTitle.Checked).Apply(); @@ -276,8 +253,8 @@ namespace GUTSchedule.Droid.Activities forMonth.Click += (s, e) => SetDate(30); forSemester.Click += (s, e) => { - Data.EndDate = DateTime.Today.Month > 8 ? new DateTime(DateTime.Today.Year + 1, 1, 1) : new DateTime(DateTime.Today.Year, 8, 31); - end.Text = Data.EndDate.ToShortDateString(); + endDate = DateTime.Today.Month > 8 ? new DateTime(DateTime.Today.Year + 1, 1, 1) : new DateTime(DateTime.Today.Year, 8, 31); + end.Text = endDate.ToShortDateString(); }; start.Click += Start_Click; diff --git a/GUT.Schedule/GUTSchedule.Droid/Activities/StartActivity.cs b/GUT.Schedule/GUTSchedule.Droid/Activities/StartActivity.cs index 1308dc6..a3f9d22 100644 --- a/GUT.Schedule/GUTSchedule.Droid/Activities/StartActivity.cs +++ b/GUT.Schedule/GUTSchedule.Droid/Activities/StartActivity.cs @@ -59,11 +59,7 @@ namespace GUT.Schedule.Droid.Activities } status.Text = Resources.GetText(Resource.String.facultiesLoadingStatus); - await Parser.LoadFaculties(); - - status.Text = Resources.GetText(Resource.String.offsetDatesLoadingStatus); - using HttpClient client = new HttpClient(); - Data.FirstWeekDay = int.Parse(await client.GetStringAsync("https://xfox111.net/schedule_offset.txt")); + MainActivity.Faculties = await Parser.GetFaculties(); } catch (HttpRequestException e) { diff --git a/GUT.Schedule/GUTSchedule.Droid/Calendar.cs b/GUT.Schedule/GUTSchedule.Droid/Calendar.cs index 987752b..b5bc255 100644 --- a/GUT.Schedule/GUTSchedule.Droid/Calendar.cs +++ b/GUT.Schedule/GUTSchedule.Droid/Calendar.cs @@ -4,6 +4,7 @@ using Android.Content; using Android.Database; using Android.Net; using Android.Provider; +using GUTSchedule.Droid.Activities; using GUTSchedule.Models; using Java.Util; @@ -42,62 +43,15 @@ namespace GUTSchedule.Droid public static void Export(IEnumerable schedule) { - DataSet data = Data.DataSet; - foreach (Occupation item in schedule) { ContentValues eventValues = new ContentValues(); - eventValues.Put(CalendarContract.Events.InterfaceConsts.CalendarId, data.Calendar); + eventValues.Put(CalendarContract.Events.InterfaceConsts.CalendarId, Calendars[MainActivity.SelectedCalendarIndex].Id); eventValues.Put(CalendarContract.Events.InterfaceConsts.Title, string.Format("{0}.{1} {2} ({3})", item.Order, - data.AddGroupToTitle ? $" [{item.Group}]" : "", - item.Name, - item.Type)); - - eventValues.Put(CalendarContract.Events.InterfaceConsts.Description, item.Professor); - eventValues.Put(CalendarContract.Events.InterfaceConsts.EventLocation, string.Join(';', item.Cabinets)); - - eventValues.Put(CalendarContract.Events.InterfaceConsts.Availability, 0); - - eventValues.Put(CalendarContract.Events.InterfaceConsts.HasAlarm, data.Reminder != -5); - // For some reason Google calendars ignore HasAlarm = false and set reminder for 30 minutes. Local calendars don't seem to have this issue - - eventValues.Put(CalendarContract.Events.InterfaceConsts.Dtstart, item.StartTime.ToUnixTime()); - eventValues.Put(CalendarContract.Events.InterfaceConsts.Dtend, item.EndTime.ToUnixTime()); - - eventValues.Put(CalendarContract.Events.InterfaceConsts.EventTimezone, TimeZone.Default.ID); - eventValues.Put(CalendarContract.Events.InterfaceConsts.EventEndTimezone, TimeZone.Default.ID); - eventValues.Put(CalendarContract.Events.InterfaceConsts.CustomAppPackage, Application.Context.PackageName); - - Uri response = Application.Context.ContentResolver.Insert(CalendarContract.Events.ContentUri, eventValues); - - // Settings reminder - if (data.Reminder != -5) - { - ContentValues reminderValues = new ContentValues(); - reminderValues.Put(CalendarContract.Reminders.InterfaceConsts.EventId, long.Parse(response.LastPathSegment)); - reminderValues.Put(CalendarContract.Reminders.InterfaceConsts.Minutes, data.Reminder); - - Application.Context.ContentResolver.Insert(CalendarContract.Reminders.ContentUri, reminderValues); - } - } - } - - public static void Export(IEnumerable schedule) - { - DataSet data = Data.DataSet; - - foreach (CabinetSubject item in schedule) - { - ContentValues eventValues = new ContentValues(); - - eventValues.Put(CalendarContract.Events.InterfaceConsts.CalendarId, data.Calendar); - - eventValues.Put(CalendarContract.Events.InterfaceConsts.Title, string.Format("{0}{1}. {2} ({3})", - item.ProfessorSchedule ? "📚 " : (data.AddGroupToTitle ? $"[{data.Group}] " : ""), - item.Order, + MainActivity.AddGroupToTitle && !string.IsNullOrWhiteSpace(item.Group) ? $" [{item.Group}]" : "", item.Name, item.Type)); @@ -106,7 +60,7 @@ namespace GUTSchedule.Droid eventValues.Put(CalendarContract.Events.InterfaceConsts.Availability, 0); - eventValues.Put(CalendarContract.Events.InterfaceConsts.HasAlarm, data.Reminder != -5); + eventValues.Put(CalendarContract.Events.InterfaceConsts.HasAlarm, MainActivity.Reminder != -5); // For some reason Google calendars ignore HasAlarm = false and set reminder for 30 minutes. Local calendars don't seem to have this issue eventValues.Put(CalendarContract.Events.InterfaceConsts.Dtstart, item.StartTime.ToUnixTime()); @@ -119,11 +73,11 @@ namespace GUTSchedule.Droid Uri response = Application.Context.ContentResolver.Insert(CalendarContract.Events.ContentUri, eventValues); // Settings reminder - if (data.Reminder != -5) + if (MainActivity.Reminder != -5) { ContentValues reminderValues = new ContentValues(); reminderValues.Put(CalendarContract.Reminders.InterfaceConsts.EventId, long.Parse(response.LastPathSegment)); - reminderValues.Put(CalendarContract.Reminders.InterfaceConsts.Minutes, data.Reminder); + reminderValues.Put(CalendarContract.Reminders.InterfaceConsts.Minutes, MainActivity.Reminder); Application.Context.ContentResolver.Insert(CalendarContract.Reminders.ContentUri, reminderValues); } diff --git a/GUT.Schedule/GUTSchedule.Droid/GUTSchedule.Droid.csproj b/GUT.Schedule/GUTSchedule.Droid/GUTSchedule.Droid.csproj index 1a12a60..f1ddd50 100644 --- a/GUT.Schedule/GUTSchedule.Droid/GUTSchedule.Droid.csproj +++ b/GUT.Schedule/GUTSchedule.Droid/GUTSchedule.Droid.csproj @@ -134,9 +134,6 @@ 12.0.3 - - 4.3.4 - diff --git a/GUT.Schedule/GUTSchedule/Extensions.cs b/GUT.Schedule/GUTSchedule/Extensions.cs index b6115a5..07c29aa 100644 --- a/GUT.Schedule/GUTSchedule/Extensions.cs +++ b/GUT.Schedule/GUTSchedule/Extensions.cs @@ -8,22 +8,6 @@ namespace GUTSchedule { public static class Extensions { - /// - /// Returns instance based on study week number, weekday and semester start day number - /// - /// Number of the study week - /// Weekday - /// instance based on study week number, weekday and semester start day number - public static DateTime GetDateFromWeeks(int week, int weekday) - { - DateTime dt = new DateTime(DateTime.Today.Year, DateTime.Today.Month >= 8 ? 9 : 2, Data.FirstWeekDay); - - dt = dt.AddDays(--week * 7); - dt = dt.AddDays(--weekday); - - return dt; - } - public static void SetContent(this HttpRequestMessage request, params (string key, string value)[] values) { if (request == null) diff --git a/GUT.Schedule/GUTSchedule/Models/DefaultExportParameters.cs b/GUT.Schedule/GUTSchedule/Models/DefaultExportParameters.cs index 0a83b91..4df6762 100644 --- a/GUT.Schedule/GUTSchedule/Models/DefaultExportParameters.cs +++ b/GUT.Schedule/GUTSchedule/Models/DefaultExportParameters.cs @@ -5,6 +5,5 @@ public string FacultyId { get; set; } public string GroupId { get; set; } public string Course { get; set; } - public bool Session { get; set; } } } \ No newline at end of file diff --git a/GUT.Schedule/GUTSchedule/Parser.cs b/GUT.Schedule/GUTSchedule/Parser.cs index cc97a5a..a11965a 100644 --- a/GUT.Schedule/GUTSchedule/Parser.cs +++ b/GUT.Schedule/GUTSchedule/Parser.cs @@ -4,6 +4,7 @@ using AngleSharp.Html.Parser; using GUTSchedule.Models; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; @@ -11,15 +12,9 @@ using System.Threading.Tasks; namespace GUTSchedule { - public enum ScheduleType - { - Default = 1, - Session = 2 - } - public static class Parser { - public static async Task VaildateAuthorization(string email, string password) + private static async Task VaildateAuthorization(string email, string password) { if (string.IsNullOrWhiteSpace(email)) throw new ArgumentNullException(nameof(email)); @@ -49,26 +44,32 @@ namespace GUTSchedule throw new System.Security.VerificationException(responseQuery["error"].Replace("|", "; ")); } + + return client; } public static async Task> GetSchedule(ExportParameters exportParameters) { List schedule = new List(); - if (exportParameters is CabinetExportParameters) + if (exportParameters is CabinetExportParameters cabinetArgs) { - - } - else if (exportParameters is DefaultExportParameters arg) - { - if (arg.Session) - schedule.AddRange(await GetSessionSchedule()); - else + HttpClient client = await VaildateAuthorization(cabinetArgs.Email, cabinetArgs.Password); + for (DateTime d = exportParameters.StartDate; d <= exportParameters.EndDate; d = d.AddMonths(1)) { - int offsetDay = int.Parse(await new HttpClient().GetStringAsync("https://xfox111.net/schedule_offset.txt")); - schedule.AddRange(await GetRegularSchedule(offsetDay, arg.FacultyId, arg.Course, arg.GroupId)); + schedule.AddRange(await GetCabinetSchedule(client, d, false)); + schedule.AddRange(await GetCabinetSchedule(client, d, true)); } } + else if (exportParameters is DefaultExportParameters args) + { + int offsetDay = int.Parse(await new HttpClient().GetStringAsync("https://xfox111.net/schedule_offset.txt")); + IHtmlDocument[] rawSchedule = await GetRawSchedule(args.FacultyId, args.Course, args.GroupId); + if(rawSchedule[0] != null) + schedule.AddRange(ParseRegularSchedule(offsetDay, rawSchedule[0])); + if(rawSchedule[1] != null) + schedule.AddRange(ParseSessionSchedule(rawSchedule[1])); + } else throw new ArgumentException("Invaild argument instance", nameof(exportParameters)); @@ -84,21 +85,41 @@ namespace GUTSchedule schedule.RemoveAt(k--); } - return schedule.FindAll(i => i.StartTime.Date >= exportParameters.StartDate && i.StartTime.Date <= exportParameters.EndDate); + schedule = schedule.FindAll(i => i.StartTime.Date >= exportParameters.StartDate && i.StartTime.Date <= exportParameters.EndDate); + if (schedule.Count < 1) + throw new NullReferenceException("Не удалось найти расписание соответствующее критериям. Ничего не экспортировано"); + + return schedule; } - public static async Task> GetFaculties(ScheduleType scheduleType) => - await GetList( + public static async Task> GetFaculties() + { + List<(string, string)> list = await GetList( ("choice", "1"), ("kurs", "0"), - ("type_z", ((int)scheduleType).ToString()), + ("type_z", "1"), ("schet", GetCurrentSemester())); - public static async Task> GetGroups(ScheduleType scheduleType, string facultyId, string course = "0") => + if (list.Count < 1) + list = await GetList( + ("choice", "1"), + ("kurs", "0"), + ("type_z", "2"), + ("schet", GetCurrentSemester())); + else + return list; + + if (list.Count < 1) + list = new List<(string, string)>(); + + return list; + } + + public static async Task> GetGroups(string facultyId, string course = "0") => await GetList( ("choice", "1"), ("kurs", course), - ("type_z", ((int)scheduleType).ToString()), + ("type_z", "1"), ("schet", GetCurrentSemester()), ("faculty", facultyId)); @@ -127,10 +148,10 @@ namespace GUTSchedule { DateTime now = DateTime.Today; - if (now.Month > 8) - return $"205.{now.Year - 2000}{now.Year - 1999}/1"; - else + if (now.Month > 1 && now.Month < 9) return $"205.{now.Year - 2001}{now.Year - 2000}/2"; + else + return $"205.{now.Year - 2000}{now.Year - 1999}/1"; } private static DateTime[] GetDatesFromWeeks(int offsetDay, int weekday, string[] weeks) @@ -150,7 +171,7 @@ namespace GUTSchedule return dates.ToArray(); } - private static async Task> GetRegularSchedule(int offsetDay, string facultyId, string course, string groupId) + private static async Task GetRawSchedule(string facultyId, string course, string groupId) { if (string.IsNullOrWhiteSpace(facultyId)) throw new ArgumentNullException(nameof(facultyId)); @@ -159,72 +180,146 @@ namespace GUTSchedule if (string.IsNullOrWhiteSpace(groupId)) throw new ArgumentNullException(nameof(groupId)); - List schedule = new List(); + IHtmlDocument[] docs = new IHtmlDocument[2]; using (HttpClient client = new HttpClient()) - { - HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "https://cabinet.sut.ru/raspisanie_all_new"); - request.SetContent( - ("group_el", "0"), - ("kurs", course), - ("type_z", "1"), - ("faculty", facultyId), - ("group", groupId), - ("ok", "Показать"), - ("schet", GetCurrentSemester())); - - request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded"); - - HttpResponseMessage response = await client.SendAsync(request); - string responseContent = await response.Content.ReadAsStringAsync(); - if (string.IsNullOrWhiteSpace(responseContent)) - return schedule; - - IHtmlDocument doc = new HtmlParser().ParseDocument(responseContent); - - string groupName = doc.QuerySelector("#group").Children.FirstOrDefault(i => i.HasAttribute("selected")).TextContent; - - IHtmlCollection pairs = doc.QuerySelectorAll(".pair"); - foreach (IElement item in pairs) + for(int k = 1; k < 3; k++) { - DateTime[] dates = GetDatesFromWeeks( - offsetDay, - int.Parse(item.GetAttribute("weekday")), - item.QuerySelector(".weeks").TextContent.Replace("(", "").Replace("н)", "").Replace(" ", "").Split(',')); + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "https://cabinet.sut.ru/raspisanie_all_new"); + request.SetContent( + ("group_el", "0"), + ("kurs", course), + ("type_z", k.ToString()), + ("faculty", facultyId), + ("group", groupId), + ("ok", "Показать"), + ("schet", GetCurrentSemester())); - foreach(DateTime date in dates) + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded"); + + HttpResponseMessage response = await client.SendAsync(request); + string responseContent = await response.Content.ReadAsStringAsync(); + if (string.IsNullOrWhiteSpace(responseContent)) + docs[k - 1] = null; + + docs[k - 1] = new HtmlParser().ParseDocument(responseContent); + } + + return docs; + } + + private static List ParseRegularSchedule(int offsetDay, IHtmlDocument raw) + { + if (raw == null) + throw new ArgumentNullException(nameof(raw)); + + List schedule = new List(); + string groupName = raw.QuerySelector("#group").Children.FirstOrDefault(i => i.HasAttribute("selected")).TextContent; + + IHtmlCollection pairs = raw.QuerySelectorAll(".pair"); + foreach (IElement item in pairs) + { + DateTime[] dates = GetDatesFromWeeks( + offsetDay, + int.Parse(item.GetAttribute("weekday")), + item.QuerySelector(".weeks").TextContent.Replace("(", "").Replace("н)", "").Replace(" ", "").Split(',')); + + foreach (DateTime date in dates) + { + int order = int.Parse(item.GetAttribute("pair")) - 1; + Occupation occupation = new Occupation { - schedule.Add(new Occupation - { - Name = item.QuerySelector(".subect").TextContent, - Type = item.QuerySelector(".type").TextContent, - Group = groupName - }); + Name = item.QuerySelector(".subect").TextContent, + Type = item.QuerySelector(".type").TextContent.Replace("(", "").Replace(")", ""), + Group = groupName, + Opponent = item.QuerySelector(".teacher")?.GetAttribute("title").Replace("; ", "\n") ?? "", + Cabinet = item.QuerySelector(".aud")?.TextContent.Replace("ауд.: ", "").Replace("; Б22", "") ?? "СПбГУТ", + Order = order > 50 ? $"Ф{order - 81}" : order.ToString() + }; + + string startTime; + switch (order) + { + case 1: + startTime = "9:00"; break; + case 2: + startTime = "10:45"; break; + case 3: + startTime = "13:00"; break; + case 4: + startTime = "14:45"; break; + case 5: + startTime = "16:30"; break; + case 6: + startTime = "18:15"; break; + case 7: + startTime = "20:00"; break; + case 29: + startTime = "20:00"; + occupation.Order = "7"; + break; + case 82: + startTime = "9:00"; break; + case 83: + startTime = "10:30"; break; + case 84: + startTime = "12:00"; break; + case 85: + startTime = "13:30"; break; + case 86: + startTime = "15:00"; break; + case 87: + startTime = "16:30"; break; + case 88: + startTime = "18:00"; break; + default: + startTime = "9:00"; break; } - string name, type, professor, place; - int order, weekday; - string[] weeks; - name = item.QuerySelector(".subect")?.TextContent ?? "Неизвестный предмет (см. Расписание)"; - type = item.QuerySelector(".type").TextContent.Replace("(", "").Replace(")", ""); - professor = item.QuerySelector(".teacher")?.GetAttribute("title").Replace(";", "") ?? ""; - place = item.QuerySelector(".aud")?.TextContent ?? "СПбГУТ"; - order = int.Parse(item.GetAttribute("pair")) - 1; - weeks = item.QuerySelector(".weeks").TextContent.Replace("(", "").Replace("н)", "").Replace(" ", "").Split(','); - weekday = int.Parse(item.GetAttribute("weekday")); + occupation.StartTime = date.Add(TimeSpan.Parse(startTime)); + occupation.EndTime = occupation.StartTime.AddMinutes(order > 10 ? 90 : 95); - schedule.AddRange(Occupation.GetSubject(name, type, professor, place, order, weeks, weekday, groupName)); + schedule.Add(occupation); } } - + return schedule; } - private static async Task> GetSessionSchedule() + private static List ParseSessionSchedule(IHtmlDocument raw) { + if (raw == null) + throw new ArgumentNullException(nameof(raw)); + List schedule = new List(); + string groupName = raw.QuerySelector("#group").Children.FirstOrDefault(i => i.HasAttribute("selected"))?.TextContent; + + IHtmlCollection pairs = raw.QuerySelectorAll(".pair"); + foreach (IElement item in pairs) + { + Occupation occupation = new Occupation + { + Name = item.QuerySelector(".subect").TextContent, + Type = item.QuerySelector(".type").TextContent, + Group = groupName, + Cabinet = item.QuerySelector(".aud").TextContent, + Opponent = item.QuerySelector(".teacher")?.GetAttribute("title"), + Order = "Сессия" + }; + + DateTime date = DateTime.Parse(item.FirstChild.FirstChild.TextContent, new CultureInfo("ru-RU")); + string rawTime = item.ChildNodes[2].TextContent; + rawTime = rawTime.Substring(rawTime.IndexOf('(')).Replace(")", "").Replace('.', ':'); + + occupation.StartTime = date.Add(TimeSpan.Parse(rawTime.Split('-')[0])); + occupation.EndTime = date.Add(TimeSpan.Parse(rawTime.Split('-')[1])); + + schedule.Add(occupation); + } + + return schedule; } - private static async Task> GetCabinetSchedule(HttpClient client, DateTime date, bool checkProfSchedule) + private static async Task> GetCabinetSchedule(HttpClient client, DateTime date, bool checkProfSchedule) { HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, $"https://cabs.itut.ru/cabinet/project/cabinet/forms/{(checkProfSchedule ? "pr_" : "")}raspisanie_kalendar.php"); request.SetContent( @@ -239,38 +334,100 @@ namespace GUTSchedule throw new HttpRequestException(responseContent); IHtmlDocument doc = new HtmlParser().ParseDocument(responseContent); - List schedule = new List(); + List schedule = new List(); - if (!checkProfSchedule) - Data.DataSet.Group = doc.QuerySelector(".style_gr b").TextContent; + string groupName = checkProfSchedule ? null : doc.QuerySelector(".style_gr b").TextContent; foreach (var i in doc.QuerySelectorAll("td").Where(i => i.GetAttribute("style") == "text-align: center; vertical-align: top")) for (int k = 0; k < i.QuerySelectorAll("i").Length; k++) { - CabinetSubject item = new CabinetSubject( - name: i.QuerySelectorAll("b")[k * 2 + 1].TextContent, - type: i.QuerySelectorAll("i")[k].TextContent, - cabinet: i.QuerySelectorAll("small")[k].NextSibling.TextContent.Replace("; Б22", ""), - opponent: i.QuerySelectorAll("i")[k].NextSibling.NextSibling.NodeType == NodeType.Text ? + Occupation item = new Occupation + { + Name = i.QuerySelectorAll("b")[k * 2 + 1].TextContent, + Type = i.QuerySelectorAll("i")[k].TextContent, + Group = groupName, + Opponent = i.QuerySelectorAll("i")[k].NextSibling.NextSibling?.NodeType == NodeType.Text ? i.QuerySelectorAll("i")[k].NextSibling.NextSibling.TextContent : "", + }; + + if (string.IsNullOrWhiteSpace(item.Opponent)) + item.Opponent = i.QuerySelectorAll("i")[k].NextSibling.NextSibling.NextSibling?.NodeType == NodeType.Text ? + i.QuerySelectorAll("i")[k].NextSibling.NextSibling.NextSibling.TextContent : ""; + + try { item.Cabinet = i.QuerySelectorAll("small")[k].NextSibling.TextContent.Replace("; Б22", ""); } + catch { item.Cabinet = "СПбГУТ"; } + + string rawTime = i.QuerySelectorAll("b")[k * 2 + 2].TextContent; + item.StartTime = new DateTime( year: date.Year, month: date.Month, day: int.Parse(i.ChildNodes[0].TextContent), - schedule: i.QuerySelectorAll("b")[k * 2 + 2].TextContent, - checkProfSchedule); - schedule.Add(item); - } + hour: int.Parse(rawTime.Split('-')[0].Split('.')[0]), + minute: int.Parse(rawTime.Split('-')[0].Split('.')[1]), + second: 0); - // Merge duplicating entries - schedule.OrderByDescending(i => i.StartTime); - for (int k = 1; k < schedule.Count; k++) - if (schedule[k - 1].StartTime == schedule[k].StartTime && - schedule[k - 1].Name == schedule[k].Name && - schedule[k - 1].Type == schedule[k].Type) - { - schedule[k - 1].Opponent += "\n" + schedule[k].Opponent; - schedule[k - 1].Cabinet += "; " + schedule[k].Cabinet; - schedule.RemoveAt(k--); + item.EndTime = new DateTime( + year: date.Year, + month: date.Month, + day: int.Parse(i.ChildNodes[0].TextContent), + hour: int.Parse(rawTime.Split('-')[1].Split('.')[0]), + minute: int.Parse(rawTime.Split('-')[1].Split('.')[1]), + second: 0); + + switch(rawTime.Split('-')[1]) + { + case "10.35": + item.Order = "1"; + break; + case "12.20": + item.Order = "2"; + break; + case "14.35": + item.Order = "3"; + break; + case "16.20": + item.Order = "4"; + break; + case "18.05": + item.Order = "5"; + break; + case "19.50": + item.Order = "6"; + break; + case "21.35": + item.Order = "7"; + break; + case "10.30": + item.Order = "Ф1"; + break; + case "12.00": + item.Order = "Ф2"; + break; + case "13.30": + item.Order = "Ф3"; + break; + case "15.00": + item.Order = "Ф4"; + break; + case "16.30": + item.Order = "Ф5"; + break; + case "18.00": + item.Order = "Ф6"; + break; + case "19.30": + item.Order = "Ф7"; + break; + + default: + item.Order = "0"; + break; + } + + if (checkProfSchedule) + item.Order = item.Order.Insert(0, "📚 "); + + schedule.Add(item); } return schedule;