1
0
mirror of https://github.com/XFox111/GUTSchedule.git synced 2026-04-22 06:58:01 +03:00

Redone parser, Fixed master schedule parsing and professors parsing issues

This commit is contained in:
Michael Gordeev
2020-02-11 17:34:16 +03:00
parent d9d24821b7
commit 43bb9a6f4f
8 changed files with 321 additions and 281 deletions
@@ -31,36 +31,12 @@ namespace GUTSchedule.Droid.Activities
{ {
try try
{ {
if (Data.DataSet.IsProfessor == true) status.Text = Resources.GetText(Resource.String.loadingStatus);
status.Text = Resources.GetText(Resource.String.potatoLoadingStatus); // For some reason professors' schedule loads much slower
else
status.Text = Resources.GetText(Resource.String.loadingStatus);
if (Data.DataSet.HttpClient != null) List<Occupation> schedule = await Parser.GetSchedule(MainActivity.ExportParameters);
{
List<CabinetSubject> schedule = new List<CabinetSubject>();
for (DateTime d = Data.StartDate; d <= Data.EndDate; d = d.AddMonths(1)) status.Text = Resources.GetText(Resource.String.calendarExportStatus);
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) Calendar.Export(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<Occupation> 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.doneStatus); status.Text = Resources.GetText(Resource.String.doneStatus);
await Task.Delay(1000); await Task.Delay(1000);
@@ -1,6 +1,5 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Net.Http;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.Content.PM; using Android.Content.PM;
@@ -10,17 +9,26 @@ using Android.Support.V7.App;
using Android.Text.Method; using Android.Text.Method;
using Android.Views; using Android.Views;
using Android.Widget; using Android.Widget;
using AngleSharp.Html.Dom;
using AngleSharp.Html.Parser;
using GUTSchedule.Models; using GUTSchedule.Models;
using GUTSchedule;
using GUTSchedule.Droid.Fragments; using GUTSchedule.Droid.Fragments;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace GUTSchedule.Droid.Activities namespace GUTSchedule.Droid.Activities
{ {
[Activity] [Activity]
public class MainActivity : AppCompatActivity 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 start, end, export;
Button forDay, forWeek, forMonth, forSemester; Button forDay, forWeek, forMonth, forSemester;
Spinner faculty, course, group, reminder, calendar; Spinner faculty, course, group, reminder, calendar;
@@ -31,7 +39,7 @@ namespace GUTSchedule.Droid.Activities
ISharedPreferences prefs; ISharedPreferences prefs;
protected override void OnCreate(Bundle savedInstanceState) protected override async void OnCreate(Bundle savedInstanceState)
{ {
base.OnCreate(savedInstanceState); base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.Main); SetContentView(Resource.Layout.Main);
@@ -42,14 +50,17 @@ namespace GUTSchedule.Droid.Activities
AssignVariables(); AssignVariables();
faculty.SetList(this, Data.Faculties.Select(i => i.Name)); faculty.SetList(this, Faculties.Select(i => i.name));
int s = Data.Faculties.FindIndex(i => i.Id == prefs.GetString("Faculty", "-123")); int s = Faculties.FindIndex(i => i.id == prefs.GetString("Faculty", "-123"));
faculty.SetSelection(s == -1 ? 0 : s); faculty.SetSelection(s == -1 ? 0 : s);
course.SetList(this, "1234".ToCharArray()); 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) 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 // P.S. Fuck Android
await Task.Delay(100);
UpdateGroupsList();
AddEvents(); AddEvents();
// Settings spinners' dropdown lists content // Settings spinners' dropdown lists content
@@ -66,8 +77,8 @@ namespace GUTSchedule.Droid.Activities
s = Calendar.Calendars.FindIndex(i => i.Id == prefs.GetString("Calendar", "-123")); s = Calendar.Calendars.FindIndex(i => i.Id == prefs.GetString("Calendar", "-123"));
calendar.SetSelection(s == -1 ? 0 : s); calendar.SetSelection(s == -1 ? 0 : s);
end.Text = Data.EndDate.ToShortDateString(); end.Text = endDate.ToShortDateString();
start.Text = Data.StartDate.ToShortDateString(); start.Text = startDate.ToShortDateString();
groupTitle.Checked = prefs.GetBoolean("AddGroupToHeader", false); groupTitle.Checked = prefs.GetBoolean("AddGroupToHeader", false);
authorize.Checked = prefs.GetBoolean("Authorize", true); authorize.Checked = prefs.GetBoolean("Authorize", true);
@@ -76,22 +87,19 @@ namespace GUTSchedule.Droid.Activities
password.Text = prefs.GetString("password", ""); 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; error.Visibility = ViewStates.Gone;
if (Data.StartDate > Data.EndDate) if (startDate > endDate)
{ {
error.Text = Resources.GetText(Resource.String.invalidDateRangeError); error.Text = Resources.GetText(Resource.String.invalidDateRangeError);
error.Visibility = ViewStates.Visible; error.Visibility = ViewStates.Visible;
return; return;
} }
HttpClient client = null;
bool? isProf = null;
if (authorize.Checked) if (authorize.Checked)
{ {
Toast.MakeText(ApplicationContext, Resources.GetText(Resource.String.authorizationState), ToastLength.Short).Show();
if (string.IsNullOrWhiteSpace(email.Text) || string.IsNullOrWhiteSpace(password.Text)) if (string.IsNullOrWhiteSpace(email.Text) || string.IsNullOrWhiteSpace(password.Text))
{ {
error.Text = Resources.GetText(Resource.String.invalidAuthorizationError); error.Text = Resources.GetText(Resource.String.invalidAuthorizationError);
@@ -99,44 +107,13 @@ namespace GUTSchedule.Droid.Activities
return; return;
} }
export.Enabled = false; ExportParameters = new CabinetExportParameters
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)
{ {
error.Text = $"{Resources.GetText(Resource.String.authorizationError)}: {response.StatusCode}: {responseContent}"; Email = email.Text,
error.Visibility = ViewStates.Visible; Password = password.Text,
return; EndDate = endDate,
} StartDate = startDate
};
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;
// Если ты это читаешь и у тебя возникли вопросы по типу "А какого хуя творится в коде ниже?!", то во-первых: // Если ты это читаешь и у тебя возникли вопросы по типу "А какого хуя творится в коде ниже?!", то во-первых:
// According to this SO thread: https://stackoverflow.com/questions/1925486/android-storing-username-and-password // According to this SO thread: https://stackoverflow.com/questions/1925486/android-storing-username-and-password
@@ -150,40 +127,40 @@ namespace GUTSchedule.Droid.Activities
} }
else else
{ {
if (Data.Groups.Count < 1) if (Groups.Count < 1)
{ {
error.Text = Resources.GetText(Resource.String.groupSelectionError); error.Text = Resources.GetText(Resource.String.groupSelectionError);
error.Visibility = ViewStates.Visible; error.Visibility = ViewStates.Visible;
return; 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 AddGroupToTitle = groupTitle.Checked;
Data.DataSet = new DataSet SelectedCalendarIndex = calendar.SelectedItemPosition;
{ Reminder = (reminder.SelectedItemPosition - 1) * 5;
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
};
StartActivity(new Intent(this, typeof(ExportActivity))); StartActivity(new Intent(this, typeof(ExportActivity)));
} }
private async void End_Click(object sender, EventArgs e) private async void End_Click(object sender, EventArgs e)
{ {
Data.EndDate = await new DatePickerFragment().GetDate(SupportFragmentManager, Data.EndDate); endDate = await new DatePickerFragment().GetDate(SupportFragmentManager, endDate);
end.Text = Data.EndDate.ToShortDateString(); end.Text = endDate.ToShortDateString();
} }
private async void Start_Click(object sender, EventArgs e) private async void Start_Click(object sender, EventArgs e)
{ {
Data.StartDate = await new DatePickerFragment().GetDate(SupportFragmentManager, Data.StartDate); startDate = await new DatePickerFragment().GetDate(SupportFragmentManager, startDate);
start.Text = Data.StartDate.ToShortDateString(); start.Text = startDate.ToShortDateString();
} }
private async void UpdateGroupsList() private async void UpdateGroupsList()
@@ -191,17 +168,17 @@ namespace GUTSchedule.Droid.Activities
if (course.SelectedItem == null) if (course.SelectedItem == null)
return; return;
await Parser.LoadGroups(Data.Faculties[faculty.SelectedItemPosition].Id, course.SelectedItemPosition + 1); Groups = await Parser.GetGroups(Faculties[faculty.SelectedItemPosition].id, (course.SelectedItemPosition + 1).ToString());
group.SetList(this, Data.Groups.Select(i => i.Name)); 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); group.SetSelection(s == -1 ? 0 : s);
} }
private void SetDate(int days) private void SetDate(int days)
{ {
Data.EndDate = Data.StartDate.AddDays(days); endDate = startDate.AddDays(days);
end.Text = Data.EndDate.ToShortDateString(); end.Text = endDate.ToShortDateString();
} }
#region Init stuff #region Init stuff
@@ -238,7 +215,7 @@ namespace GUTSchedule.Droid.Activities
{ {
faculty.ItemSelected += (s, e) => faculty.ItemSelected += (s, e) =>
{ {
prefs.Edit().PutString("Faculty", Data.Faculties[e.Position].Id).Apply(); prefs.Edit().PutString("Faculty", Faculties[e.Position].id).Apply();
UpdateGroupsList(); UpdateGroupsList();
}; };
course.ItemSelected += (s, e) => course.ItemSelected += (s, e) =>
@@ -265,7 +242,7 @@ namespace GUTSchedule.Droid.Activities
reminder.ItemSelected += (s, e) => reminder.ItemSelected += (s, e) =>
prefs.Edit().PutInt("Reminder", e.Position).Apply(); prefs.Edit().PutInt("Reminder", e.Position).Apply();
group.ItemSelected += (s, e) => 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) => groupTitle.Click += (s, e) =>
prefs.Edit().PutBoolean("AddGroupToHeader", groupTitle.Checked).Apply(); prefs.Edit().PutBoolean("AddGroupToHeader", groupTitle.Checked).Apply();
@@ -276,8 +253,8 @@ namespace GUTSchedule.Droid.Activities
forMonth.Click += (s, e) => SetDate(30); forMonth.Click += (s, e) => SetDate(30);
forSemester.Click += (s, e) => 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); 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(); end.Text = endDate.ToShortDateString();
}; };
start.Click += Start_Click; start.Click += Start_Click;
@@ -59,11 +59,7 @@ namespace GUT.Schedule.Droid.Activities
} }
status.Text = Resources.GetText(Resource.String.facultiesLoadingStatus); status.Text = Resources.GetText(Resource.String.facultiesLoadingStatus);
await Parser.LoadFaculties(); MainActivity.Faculties = await Parser.GetFaculties();
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"));
} }
catch (HttpRequestException e) catch (HttpRequestException e)
{ {
+6 -52
View File
@@ -4,6 +4,7 @@ using Android.Content;
using Android.Database; using Android.Database;
using Android.Net; using Android.Net;
using Android.Provider; using Android.Provider;
using GUTSchedule.Droid.Activities;
using GUTSchedule.Models; using GUTSchedule.Models;
using Java.Util; using Java.Util;
@@ -42,62 +43,15 @@ namespace GUTSchedule.Droid
public static void Export(IEnumerable<Occupation> schedule) public static void Export(IEnumerable<Occupation> schedule)
{ {
DataSet data = Data.DataSet;
foreach (Occupation item in schedule) foreach (Occupation item in schedule)
{ {
ContentValues eventValues = new ContentValues(); 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})", eventValues.Put(CalendarContract.Events.InterfaceConsts.Title, string.Format("{0}.{1} {2} ({3})",
item.Order, item.Order,
data.AddGroupToTitle ? $" [{item.Group}]" : "", MainActivity.AddGroupToTitle && !string.IsNullOrWhiteSpace(item.Group) ? $" [{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<CabinetSubject> 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,
item.Name, item.Name,
item.Type)); item.Type));
@@ -106,7 +60,7 @@ namespace GUTSchedule.Droid
eventValues.Put(CalendarContract.Events.InterfaceConsts.Availability, 0); 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 // 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.Dtstart, item.StartTime.ToUnixTime());
@@ -119,11 +73,11 @@ namespace GUTSchedule.Droid
Uri response = Application.Context.ContentResolver.Insert(CalendarContract.Events.ContentUri, eventValues); Uri response = Application.Context.ContentResolver.Insert(CalendarContract.Events.ContentUri, eventValues);
// Settings reminder // Settings reminder
if (data.Reminder != -5) if (MainActivity.Reminder != -5)
{ {
ContentValues reminderValues = new ContentValues(); ContentValues reminderValues = new ContentValues();
reminderValues.Put(CalendarContract.Reminders.InterfaceConsts.EventId, long.Parse(response.LastPathSegment)); 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); Application.Context.ContentResolver.Insert(CalendarContract.Reminders.ContentUri, reminderValues);
} }
@@ -134,9 +134,6 @@
<PackageReference Include="Newtonsoft.Json"> <PackageReference Include="Newtonsoft.Json">
<Version>12.0.3</Version> <Version>12.0.3</Version>
</PackageReference> </PackageReference>
<PackageReference Include="System.Net.Http">
<Version>4.3.4</Version>
</PackageReference>
<PackageReference Include="Xamarin.Android.Support.Design" Version="28.0.0.3" /> <PackageReference Include="Xamarin.Android.Support.Design" Version="28.0.0.3" />
<PackageReference Include="Xamarin.Android.Support.Core.Utils" Version="28.0.0.3" /> <PackageReference Include="Xamarin.Android.Support.Core.Utils" Version="28.0.0.3" />
<PackageReference Include="Xamarin.Android.Support.CustomTabs" Version="28.0.0.3" /> <PackageReference Include="Xamarin.Android.Support.CustomTabs" Version="28.0.0.3" />
-16
View File
@@ -8,22 +8,6 @@ namespace GUTSchedule
{ {
public static class Extensions public static class Extensions
{ {
/// <summary>
/// Returns <see cref="DateTime"/> instance based on study week number, weekday and semester start day number
/// </summary>
/// <param name="week">Number of the study week</param>
/// <param name="weekday">Weekday</param>
/// <returns><see cref="DateTime"/> instance based on study week number, weekday and semester start day number</returns>
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) public static void SetContent(this HttpRequestMessage request, params (string key, string value)[] values)
{ {
if (request == null) if (request == null)
@@ -5,6 +5,5 @@
public string FacultyId { get; set; } public string FacultyId { get; set; }
public string GroupId { get; set; } public string GroupId { get; set; }
public string Course { get; set; } public string Course { get; set; }
public bool Session { get; set; }
} }
} }
+255 -98
View File
@@ -4,6 +4,7 @@ using AngleSharp.Html.Parser;
using GUTSchedule.Models; using GUTSchedule.Models;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
@@ -11,15 +12,9 @@ using System.Threading.Tasks;
namespace GUTSchedule namespace GUTSchedule
{ {
public enum ScheduleType
{
Default = 1,
Session = 2
}
public static class Parser public static class Parser
{ {
public static async Task VaildateAuthorization(string email, string password) private static async Task<HttpClient> VaildateAuthorization(string email, string password)
{ {
if (string.IsNullOrWhiteSpace(email)) if (string.IsNullOrWhiteSpace(email))
throw new ArgumentNullException(nameof(email)); throw new ArgumentNullException(nameof(email));
@@ -49,26 +44,32 @@ namespace GUTSchedule
throw new System.Security.VerificationException(responseQuery["error"].Replace("|", "; ")); throw new System.Security.VerificationException(responseQuery["error"].Replace("|", "; "));
} }
return client;
} }
public static async Task<List<Occupation>> GetSchedule(ExportParameters exportParameters) public static async Task<List<Occupation>> GetSchedule(ExportParameters exportParameters)
{ {
List<Occupation> schedule = new List<Occupation>(); List<Occupation> schedule = new List<Occupation>();
if (exportParameters is CabinetExportParameters) if (exportParameters is CabinetExportParameters cabinetArgs)
{ {
HttpClient client = await VaildateAuthorization(cabinetArgs.Email, cabinetArgs.Password);
} for (DateTime d = exportParameters.StartDate; d <= exportParameters.EndDate; d = d.AddMonths(1))
else if (exportParameters is DefaultExportParameters arg)
{
if (arg.Session)
schedule.AddRange(await GetSessionSchedule());
else
{ {
int offsetDay = int.Parse(await new HttpClient().GetStringAsync("https://xfox111.net/schedule_offset.txt")); schedule.AddRange(await GetCabinetSchedule(client, d, false));
schedule.AddRange(await GetRegularSchedule(offsetDay, arg.FacultyId, arg.Course, arg.GroupId)); 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 else
throw new ArgumentException("Invaild argument instance", nameof(exportParameters)); throw new ArgumentException("Invaild argument instance", nameof(exportParameters));
@@ -84,21 +85,41 @@ namespace GUTSchedule
schedule.RemoveAt(k--); 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<List<(string id, string name)>> GetFaculties(ScheduleType scheduleType) => public static async Task<List<(string id, string name)>> GetFaculties()
await GetList( {
List<(string, string)> list = await GetList(
("choice", "1"), ("choice", "1"),
("kurs", "0"), ("kurs", "0"),
("type_z", ((int)scheduleType).ToString()), ("type_z", "1"),
("schet", GetCurrentSemester())); ("schet", GetCurrentSemester()));
public static async Task<List<(string id, string name)>> 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<List<(string id, string name)>> GetGroups(string facultyId, string course = "0") =>
await GetList( await GetList(
("choice", "1"), ("choice", "1"),
("kurs", course), ("kurs", course),
("type_z", ((int)scheduleType).ToString()), ("type_z", "1"),
("schet", GetCurrentSemester()), ("schet", GetCurrentSemester()),
("faculty", facultyId)); ("faculty", facultyId));
@@ -127,10 +148,10 @@ namespace GUTSchedule
{ {
DateTime now = DateTime.Today; DateTime now = DateTime.Today;
if (now.Month > 8) if (now.Month > 1 && now.Month < 9)
return $"205.{now.Year - 2000}{now.Year - 1999}/1";
else
return $"205.{now.Year - 2001}{now.Year - 2000}/2"; 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) private static DateTime[] GetDatesFromWeeks(int offsetDay, int weekday, string[] weeks)
@@ -150,7 +171,7 @@ namespace GUTSchedule
return dates.ToArray(); return dates.ToArray();
} }
private static async Task<List<Occupation>> GetRegularSchedule(int offsetDay, string facultyId, string course, string groupId) private static async Task<IHtmlDocument[]> GetRawSchedule(string facultyId, string course, string groupId)
{ {
if (string.IsNullOrWhiteSpace(facultyId)) if (string.IsNullOrWhiteSpace(facultyId))
throw new ArgumentNullException(nameof(facultyId)); throw new ArgumentNullException(nameof(facultyId));
@@ -159,72 +180,146 @@ namespace GUTSchedule
if (string.IsNullOrWhiteSpace(groupId)) if (string.IsNullOrWhiteSpace(groupId))
throw new ArgumentNullException(nameof(groupId)); throw new ArgumentNullException(nameof(groupId));
List<Occupation> schedule = new List<Occupation>(); IHtmlDocument[] docs = new IHtmlDocument[2];
using (HttpClient client = new HttpClient()) using (HttpClient client = new HttpClient())
{ for(int k = 1; k < 3; k++)
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<IElement> pairs = doc.QuerySelectorAll(".pair");
foreach (IElement item in pairs)
{ {
DateTime[] dates = GetDatesFromWeeks( HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "https://cabinet.sut.ru/raspisanie_all_new");
offsetDay, request.SetContent(
int.Parse(item.GetAttribute("weekday")), ("group_el", "0"),
item.QuerySelector(".weeks").TextContent.Replace("(", "").Replace("н)", "").Replace(" ", "").Split(',')); ("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<Occupation> ParseRegularSchedule(int offsetDay, IHtmlDocument raw)
{
if (raw == null)
throw new ArgumentNullException(nameof(raw));
List<Occupation> schedule = new List<Occupation>();
string groupName = raw.QuerySelector("#group").Children.FirstOrDefault(i => i.HasAttribute("selected")).TextContent;
IHtmlCollection<IElement> 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.Replace("(", "").Replace(")", ""),
Name = item.QuerySelector(".subect").TextContent, Group = groupName,
Type = item.QuerySelector(".type").TextContent, Opponent = item.QuerySelector(".teacher")?.GetAttribute("title").Replace("; ", "\n") ?? "",
Group = groupName 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 ?? "Неизвестный предмет (см. Расписание)"; occupation.StartTime = date.Add(TimeSpan.Parse(startTime));
type = item.QuerySelector(".type").TextContent.Replace("(", "").Replace(")", ""); occupation.EndTime = occupation.StartTime.AddMinutes(order > 10 ? 90 : 95);
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"));
schedule.AddRange(Occupation.GetSubject(name, type, professor, place, order, weeks, weekday, groupName)); schedule.Add(occupation);
} }
} }
return schedule; return schedule;
} }
private static async Task<List<Occupation>> GetSessionSchedule() private static List<Occupation> ParseSessionSchedule(IHtmlDocument raw)
{ {
if (raw == null)
throw new ArgumentNullException(nameof(raw));
List<Occupation> schedule = new List<Occupation>();
string groupName = raw.QuerySelector("#group").Children.FirstOrDefault(i => i.HasAttribute("selected"))?.TextContent;
IHtmlCollection<IElement> 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<List<CabinetSubject>> GetCabinetSchedule(HttpClient client, DateTime date, bool checkProfSchedule) private static async Task<List<Occupation>> 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"); HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, $"https://cabs.itut.ru/cabinet/project/cabinet/forms/{(checkProfSchedule ? "pr_" : "")}raspisanie_kalendar.php");
request.SetContent( request.SetContent(
@@ -239,38 +334,100 @@ namespace GUTSchedule
throw new HttpRequestException(responseContent); throw new HttpRequestException(responseContent);
IHtmlDocument doc = new HtmlParser().ParseDocument(responseContent); IHtmlDocument doc = new HtmlParser().ParseDocument(responseContent);
List<CabinetSubject> schedule = new List<CabinetSubject>(); List<Occupation> schedule = new List<Occupation>();
if (!checkProfSchedule) string groupName = checkProfSchedule ? null : doc.QuerySelector(".style_gr b").TextContent;
Data.DataSet.Group = doc.QuerySelector(".style_gr b").TextContent;
foreach (var i in doc.QuerySelectorAll("td").Where(i => i.GetAttribute("style") == "text-align: center; vertical-align: top")) 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++) for (int k = 0; k < i.QuerySelectorAll("i").Length; k++)
{ {
CabinetSubject item = new CabinetSubject( Occupation item = new Occupation
name: i.QuerySelectorAll("b")[k * 2 + 1].TextContent, {
type: i.QuerySelectorAll("i")[k].TextContent, Name = i.QuerySelectorAll("b")[k * 2 + 1].TextContent,
cabinet: i.QuerySelectorAll("small")[k].NextSibling.TextContent.Replace("; Б22", ""), Type = i.QuerySelectorAll("i")[k].TextContent,
opponent: i.QuerySelectorAll("i")[k].NextSibling.NextSibling.NodeType == NodeType.Text ? Group = groupName,
Opponent = i.QuerySelectorAll("i")[k].NextSibling.NextSibling?.NodeType == NodeType.Text ?
i.QuerySelectorAll("i")[k].NextSibling.NextSibling.TextContent : "", 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, year: date.Year,
month: date.Month, month: date.Month,
day: int.Parse(i.ChildNodes[0].TextContent), day: int.Parse(i.ChildNodes[0].TextContent),
schedule: i.QuerySelectorAll("b")[k * 2 + 2].TextContent, hour: int.Parse(rawTime.Split('-')[0].Split('.')[0]),
checkProfSchedule); minute: int.Parse(rawTime.Split('-')[0].Split('.')[1]),
schedule.Add(item); second: 0);
}
// Merge duplicating entries item.EndTime = new DateTime(
schedule.OrderByDescending(i => i.StartTime); year: date.Year,
for (int k = 1; k < schedule.Count; k++) month: date.Month,
if (schedule[k - 1].StartTime == schedule[k].StartTime && day: int.Parse(i.ChildNodes[0].TextContent),
schedule[k - 1].Name == schedule[k].Name && hour: int.Parse(rawTime.Split('-')[1].Split('.')[0]),
schedule[k - 1].Type == schedule[k].Type) minute: int.Parse(rawTime.Split('-')[1].Split('.')[1]),
{ second: 0);
schedule[k - 1].Opponent += "\n" + schedule[k].Opponent;
schedule[k - 1].Cabinet += "; " + schedule[k].Cabinet; switch(rawTime.Split('-')[1])
schedule.RemoveAt(k--); {
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; return schedule;