diff --git a/BetterIServ.Backend/Controllers/AuthController.cs b/BetterIServ.Backend/Controllers/IServController.cs similarity index 63% rename from BetterIServ.Backend/Controllers/AuthController.cs rename to BetterIServ.Backend/Controllers/IServController.cs index 763bf82..4ad0694 100644 --- a/BetterIServ.Backend/Controllers/AuthController.cs +++ b/BetterIServ.Backend/Controllers/IServController.cs @@ -1,4 +1,5 @@ using BetterIServ.Backend.Entities; +using HtmlAgilityPack; using Microsoft.AspNetCore.Mvc; using PuppeteerSharp; using Credentials = BetterIServ.Backend.Entities.Credentials; @@ -6,7 +7,7 @@ using Credentials = BetterIServ.Backend.Entities.Credentials; namespace BetterIServ.Backend.Controllers; [ApiController] -[Route("auth")] +[Route("iserv")] public class AuthController : ControllerBase { [HttpPost("login")] @@ -47,4 +48,28 @@ public class AuthController : ControllerBase { return authKeys; } + [HttpPost("groups")] + public async Task>> GetCourses([FromBody] AuthKeys keys, [FromQuery] string domain) { + var client = new HttpClient(); + var request = new HttpRequestMessage { + Method = HttpMethod.Get, + RequestUri = new Uri($"https://{domain}/iserv/profile"), + Headers = { + { "cookie", keys.ToCookieString() } + } + }; + var raw = await (await client.SendAsync(request)).Content.ReadAsStringAsync(); + var html = new HtmlDocument(); + html.LoadHtml(raw); + + var list = html.DocumentNode.SelectSingleNode("//body/div/div[2]/div[3]/div/div/div[2]/div/div/div/div/ul[1]"); + var courses = new List(); + foreach (var child in list.ChildNodes) { + if (child.ChildNodes.Count < 1) continue; + courses.Add(child.ChildNodes[0].InnerText); + } + + return new SingleResult { Value = courses.ToArray() }; + } + } \ No newline at end of file diff --git a/BetterIServ.Backend/Controllers/UnitsController.cs b/BetterIServ.Backend/Controllers/UnitsController.cs index a7edd57..5f01f82 100644 --- a/BetterIServ.Backend/Controllers/UnitsController.cs +++ b/BetterIServ.Backend/Controllers/UnitsController.cs @@ -42,16 +42,32 @@ public class UnitsController : ControllerBase { if (node.ChildNodes.Count < 9) continue; var substitution = new Substitution { - Class = node.ChildNodes[0].InnerText, Times = node.ChildNodes[1].InnerText.Split(" - ").Select(int.Parse).ToArray(), Type = node.ChildNodes[2].InnerText, Representative = node.ChildNodes[3].InnerText, - Lesson = node.ChildNodes[4].InnerText, + NewLesson = node.ChildNodes[4].InnerText, + Lesson = node.ChildNodes[5].InnerText, Room = node.ChildNodes[6].InnerText, Teacher = node.ChildNodes[7].InnerText, Description = node.ChildNodes[9].InnerText }; + var classes = node.ChildNodes[0].InnerText; + + if (!classes.StartsWith("Q")) { + string grade = new string(classes.ToCharArray().Where(char.IsNumber).ToArray()); + var subClasses = classes.Replace(grade, "").ToCharArray(); + var result = new string[subClasses.Length]; + + for (int j = 0; j < subClasses.Length; j++) { + result[j] = grade + subClasses[j]; + } + substitution.Classes = result; + } + else { + substitution.Classes = new[] { classes }; + } + data.Substitutions.Add(substitution); } diff --git a/BetterIServ.Backend/Entities/AuthKeys.cs b/BetterIServ.Backend/Entities/AuthKeys.cs index 6852b9a..5aceeb1 100644 --- a/BetterIServ.Backend/Entities/AuthKeys.cs +++ b/BetterIServ.Backend/Entities/AuthKeys.cs @@ -6,4 +6,8 @@ public struct AuthKeys { public string AuthSid { get; set; } public string SatId { get; set; } public string AuthSession { get; set; } + + public string ToCookieString() { + return $"IServSession={Session}; IServSAT={Sat}; IServAuthSID={AuthSid}; IServSATId={SatId}; IServAuthSession={AuthSession}"; + } } \ No newline at end of file diff --git a/BetterIServ.Backend/Entities/Substitution.cs b/BetterIServ.Backend/Entities/Substitution.cs index e285a39..bf0a091 100644 --- a/BetterIServ.Backend/Entities/Substitution.cs +++ b/BetterIServ.Backend/Entities/Substitution.cs @@ -1,11 +1,12 @@ namespace BetterIServ.Backend.Entities; public struct Substitution { - public string Class { get; set; } + public string[] Classes { get; set; } public int[] Times { get; set; } public string Type { get; set; } public string Representative { get; set; } public string Lesson { get; set; } + public string NewLesson { get; set; } public string Room { get; set; } public string Teacher { get; set; } public string Description { get; set; } diff --git a/BetterIServ.Mobile/src/app/api/iserv.service.ts b/BetterIServ.Mobile/src/app/api/iserv.service.ts index 1b928aa..07f05f4 100644 --- a/BetterIServ.Mobile/src/app/api/iserv.service.ts +++ b/BetterIServ.Mobile/src/app/api/iserv.service.ts @@ -9,6 +9,7 @@ import {firstValueFrom} from "rxjs"; export class IServService { public userdata?: Userdata; + public keys?: AuthKeys; public backend: string = "http://localhost:5273"; constructor(private client: HttpClient) { @@ -16,6 +17,11 @@ export class IServService { if (data != null) { this.userdata = JSON.parse(data); } + + const keys = localStorage.getItem("keys"); + if (keys != null) { + this.keys = JSON.parse(keys); + } } public async login(email: string, password: string): Promise { @@ -27,8 +33,9 @@ export class IServService { }; try { - await firstValueFrom(this.client.post(this.backend + "/auth/login", this.userdata)); + const keys = await firstValueFrom(this.client.post(this.backend + "/iserv/login", this.userdata)); localStorage.setItem("userdata", JSON.stringify(this.userdata)); + localStorage.setItem("keys", JSON.stringify(keys)); return true; }catch (error) { return false; @@ -36,7 +43,18 @@ export class IServService { } public async getKeys(): Promise { - return await firstValueFrom(this.client.post(this.backend + "/auth/login", this.userdata)); + const keys = await firstValueFrom(this.client.post(this.backend + "/iserv/login", this.userdata)); + localStorage.setItem("keys", JSON.stringify(keys)); + return keys; + } + + public async getGroups(): Promise { + try { + return (await firstValueFrom(this.client.post<{value: string[]}>(this.backend + "/iserv/groups?domain=" + this.userdata.domain, this.keys))).value; + } catch { + await this.getKeys(); + return (await firstValueFrom(this.client.post<{value: string[]}>(this.backend + "/iserv/groups?domain=" + this.userdata.domain, this.keys))).value; + } } } diff --git a/BetterIServ.Mobile/src/app/api/units.service.ts b/BetterIServ.Mobile/src/app/api/units.service.ts index bbd6e1d..1f8b7bf 100644 --- a/BetterIServ.Mobile/src/app/api/units.service.ts +++ b/BetterIServ.Mobile/src/app/api/units.service.ts @@ -9,15 +9,24 @@ import {firstValueFrom} from "rxjs"; }) export class UnitsService { - private schools: {[domain: string]: {today: string, tomorrow: string}} = { + public schools: {[domain: string]: {today: string, tomorrow: string, classes: string[]}} = { ["hgbp.de"]: { today: "https://www.humboldt-gymnasium.de/vertretungsplan/PlanINet/heute/subst_001.htm", - tomorrow: "https://www.humboldt-gymnasium.de/vertretungsplan/PlanINet/morgen/subst_001.htm" + tomorrow: "https://www.humboldt-gymnasium.de/vertretungsplan/PlanINet/morgen/subst_001.htm", + classes: ["5a", "5b", "5c", "5d", "6a", "6b", "6c", "6d", "7a", "7b", "7c", "7d", "8a", "8b", "8c", "8d", "9a", "9b", "9c", "9d", "10a", "10b", "10c", "10d", "11a", "11b", "11c", "11d", "Q1", "Q2"] } } constructor(private iserv: IServService, private client: HttpClient) {} + public doesSchoolExist(): boolean { + return this.schools[this.iserv.userdata.domain] != undefined; + } + + public getClasses(): string[] { + return this.schools[this.iserv.userdata.domain]?.classes; + } + public async getSubstitutionPlan(date: "today" | "tomorrow"): Promise { if (this.schools[this.iserv.userdata.domain] == undefined) return undefined; const url = this.schools[this.iserv.userdata.domain][date]; diff --git a/BetterIServ.Mobile/src/app/entities/substitution.ts b/BetterIServ.Mobile/src/app/entities/substitution.ts index 4360b79..1c3452b 100644 --- a/BetterIServ.Mobile/src/app/entities/substitution.ts +++ b/BetterIServ.Mobile/src/app/entities/substitution.ts @@ -1,9 +1,10 @@ export interface Substitution { - class: string; + classes: string[]; times: number[]; type: string; representative: string; lesson: string; + newLesson: string; room: string; teacher: string; description: string; diff --git a/BetterIServ.Mobile/src/app/pages/mails/mails.page.html b/BetterIServ.Mobile/src/app/pages/mails/mails.page.html index fa38ca1..f1b7641 100644 --- a/BetterIServ.Mobile/src/app/pages/mails/mails.page.html +++ b/BetterIServ.Mobile/src/app/pages/mails/mails.page.html @@ -44,11 +44,13 @@ - - - {{folder.name}} - - + + + + {{folder.name}} + + + diff --git a/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.html b/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.html index 42f6107..e69e5ce 100644 --- a/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.html +++ b/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.html @@ -3,15 +3,7 @@ - - - - Heute - - - Morgen - - + Vertretungsplan @@ -22,9 +14,14 @@ - - - + + + Heute + + + Morgen + +
@@ -35,16 +32,26 @@
+ + + Alle Klassen + + + + + Nur eigene Kurse anzeigen + + {{subs.times.join(" - ")}}
{{subs.type}} - +
diff --git a/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.scss b/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.scss index 9930ba4..22c8c07 100644 --- a/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.scss +++ b/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.scss @@ -26,8 +26,8 @@ --background: var(--ion-color-success-shade); } - &.bittebeachten { - --background: var(--ion-color-warning-shade); + &.bittebeachten, &.stregulärem { + --background: var(--ion-color-tertiary-shade); } &.Vertretung { @@ -39,7 +39,7 @@ } &.Verlegung { - --background: var(--ion-color-secondary-shade); + --background: var(--ion-color-warning-shade); } &.hide { diff --git a/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.ts b/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.ts index 1446b78..df978e0 100644 --- a/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.ts +++ b/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.ts @@ -1,9 +1,10 @@ import { Component, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; -import { IonicModule } from '@ionic/angular'; +import {AlertController, IonicModule} from '@ionic/angular'; import {UnitsService} from "../../api/units.service"; -import {UnitsData} from "../../entities/substitution"; +import {Substitution, UnitsData} from "../../entities/substitution"; +import {IServService} from "../../api/iserv.service"; @Component({ selector: 'app-substitution', @@ -16,36 +17,89 @@ export class SubstitutionPage implements OnInit { public data: UnitsData; public showNews: boolean = false; + public courses: string[] = []; public currentClass: string; + public filterByClasses: boolean = false; - constructor(private units: UnitsService) { - this.currentClass = localStorage.getItem("class"); + constructor(public units: UnitsService, private iserv: IServService, private alerts: AlertController) { + this.currentClass = localStorage.getItem("class") || 'all'; + this.filterByClasses = localStorage.getItem("filterByClasses") == "true"; } async ngOnInit() { + if (!this.units.doesSchoolExist()) { + const alert = await this.alerts.create({ + subHeader: "Fehler", + message: "Deine Schule wird nicht unterstützt!", + buttons: ["Ok"] + }); + + await alert.present(); + return; + } + this.data = await this.units.getSubstitutionPlan("today"); + + const groups = await this.iserv.getGroups(); + for (let group of groups) { + if (!group.includes(".")) continue; + this.courses.push(group.split(".")[1]); + } } public async changeDate(date: string) { this.data = await this.units.getSubstitutionPlan(date as "today" | "tomorrow"); } - public getDistinctClasses(): string[] { - const classes: string[] = []; - if (this.data == undefined) return []; - - for (let subs of this.data.substitutions) { - if (classes.indexOf(subs.class) == -1) { - classes.push(subs.class) - } - } - - return classes; - } - public changeClass(className: string) { this.currentClass = className; localStorage.setItem("class", className); + + this.filterByClasses = false; + this.showOnlyCourses(false); + } + + public getDetails(subs: Substitution): string { + if (subs.type == "bitte beachten") { + const desc = subs.description != " " ? ' - ' + subs.description : ""; + let info = `${subs.lesson} (${subs.teacher}) in ${subs.room}`; + + if (subs.lesson != subs.newLesson) { + info = `${subs.newLesson} (${subs.representative}) statt ${subs.lesson} (${subs.teacher}) in ${subs.room}`; + } + + return info + desc; + } + + switch (subs.type) { + case "Vertretung": + case "st. regulärem Unt.": + return `${subs.lesson} (${subs.representative} statt ${subs.teacher}) in ${subs.room}`; + + case "Raumtausch": + return `${subs.lesson} (${subs.teacher}) in ${subs.room}`; + + case "Entfall": + return `${subs.lesson} (${subs.teacher})`; + + case "Stillarbeit": + return `${subs.lesson} (${subs.teacher}) in ${subs.room}`; + + case "Verlegung": + return `${subs.newLesson} (${subs.representative}) statt ${subs.lesson} (${subs.teacher}) in ${subs.room}`; + + default: + return subs.lesson + ' (' + subs.teacher + ') ' + subs.room; + } + } + + public showOnlyCourses(toggle: boolean) { + localStorage.setItem("filterByClasses", toggle.toString()); + } + + public hasClass(course: string): boolean { + if (!this.filterByClasses) return true; + return this.courses.includes(course); } } diff --git a/BetterIServ.Mobile/src/global.scss b/BetterIServ.Mobile/src/global.scss index 3cc54b2..fd64f61 100644 --- a/BetterIServ.Mobile/src/global.scss +++ b/BetterIServ.Mobile/src/global.scss @@ -33,10 +33,6 @@ ion-menu-button { cursor: pointer; } -ion-select { - padding-inline: 15px; -} - .active { color: var(--ion-color-primary); }