diff --git a/BackendMetadata/exercises.txt b/BackendMetadata/exercises.txt
new file mode 100644
index 0000000..9707fdd
--- /dev/null
+++ b/BackendMetadata/exercises.txt
@@ -0,0 +1,6 @@
+GET https://hgbp.de/iserv/exercise.csv
+Payload: HEADER(cookie)[IServSession={sessionKey}; IServSAT={satKey};]
+
+ON_SUCCESS:
+STATUS 200
+CSV RESPONSE
\ No newline at end of file
diff --git a/BackendMetadata/login.txt b/BackendMetadata/login.txt
index bf9b173..b9c498a 100644
--- a/BackendMetadata/login.txt
+++ b/BackendMetadata/login.txt
@@ -2,5 +2,10 @@ POST https://hgbp.de/iserv/auth/login
Payload: FORMDATA(_username: STRING, _password: STRING)
ON_SUCCESS:
-STATUS 302
-SessionCookie: HEADER(set-cookie)[IServAuthSession={key};...]
\ No newline at end of file
+
+FOLLOW REDIRECT
+EXTRACT avatar URL
+MAKE REQUEST
+FOLLOW REDIRECTS
+
+Tokens: HEADER(Set-Cookie)[IServSAT={sat-Key}; IServSession={sessionKey}]
diff --git a/BetterIServ.Backend/BetterIServ.Backend.csproj b/BetterIServ.Backend/BetterIServ.Backend.csproj
index c275bf3..b5be73c 100644
--- a/BetterIServ.Backend/BetterIServ.Backend.csproj
+++ b/BetterIServ.Backend/BetterIServ.Backend.csproj
@@ -9,7 +9,9 @@
+
+
diff --git a/BetterIServ.Backend/Controllers/AuthController.cs b/BetterIServ.Backend/Controllers/AuthController.cs
new file mode 100644
index 0000000..763bf82
--- /dev/null
+++ b/BetterIServ.Backend/Controllers/AuthController.cs
@@ -0,0 +1,50 @@
+using BetterIServ.Backend.Entities;
+using Microsoft.AspNetCore.Mvc;
+using PuppeteerSharp;
+using Credentials = BetterIServ.Backend.Entities.Credentials;
+
+namespace BetterIServ.Backend.Controllers;
+
+[ApiController]
+[Route("auth")]
+public class AuthController : ControllerBase {
+
+ [HttpPost("login")]
+ public async Task> GetAuthKeysV2([FromBody] Credentials credentials) {
+ await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions { Headless = true });
+ await using var page = await browser.NewPageAsync();
+ await page.GoToAsync($"https://{credentials.Domain}/iserv/auth/login");
+
+ await page.WaitForSelectorAsync("#form-username > input");
+ await page.Keyboard.TypeAsync(credentials.Username);
+ await page.Keyboard.PressAsync("Tab");
+ await page.Keyboard.TypeAsync(credentials.Password);
+ await page.ClickAsync("body > div > main > div > div.panel-body > form > div.row > div:nth-child(1) > button");
+ await Task.Delay(500);
+
+ var authKeys = new AuthKeys();
+ var cookies = await page.GetCookiesAsync();
+ foreach (var cookie in cookies) {
+ switch (cookie.Name) {
+ case "IServSession":
+ authKeys.Session = cookie.Value;
+ break;
+ case "IServSAT":
+ authKeys.Sat = cookie.Value;
+ break;
+ case "IServAuthSID":
+ authKeys.AuthSid = cookie.Value;
+ break;
+ case "IServSATId":
+ authKeys.SatId = cookie.Value;
+ break;
+ case "IServAuthSession":
+ authKeys.AuthSession = cookie.Value;
+ break;
+ }
+ }
+
+ return authKeys;
+ }
+
+}
\ No newline at end of file
diff --git a/BetterIServ.Backend/Controllers/HelperController.cs b/BetterIServ.Backend/Controllers/HelperController.cs
deleted file mode 100644
index 27f70d9..0000000
--- a/BetterIServ.Backend/Controllers/HelperController.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using System.Net;
-using Microsoft.AspNetCore.Mvc;
-
-namespace BetterIServ.Backend.Controllers;
-
-[ApiController]
-public class HelperController : ControllerBase {
-
- [HttpPost("/login")]
- public async Task> Login([FromForm] string email, [FromForm] string password) {
- try {
- using var client = new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip, AllowAutoRedirect = false }) { Timeout = TimeSpan.FromSeconds(5) };
-
- var split = email.Split("@");
- var username = split[0];
- var domain = split[1];
-
- var form = new FormUrlEncodedContent(new[] {
- new KeyValuePair("_username", username),
- new KeyValuePair("_password", password)
- });
-
- var request = new HttpRequestMessage {
- RequestUri = new Uri($"https://{domain}/iserv/auth/login"),
- Method = HttpMethod.Post,
- Content = form
- };
-
- var response = await client.SendAsync(request);
- var header = response.Headers.GetValues("Set-Cookie").First();
- var part = header.Split(";")[0];
-
- if (!part.Contains("IServAuthSession")) throw new Exception();
-
- return Ok(part.Replace("IServAuthSession=", ""));
- }
- catch (Exception) {
- return Unauthorized();
- }
- }
-
-}
\ No newline at end of file
diff --git a/BetterIServ.Backend/Controllers/UnitsController.cs b/BetterIServ.Backend/Controllers/UnitsController.cs
new file mode 100644
index 0000000..a7edd57
--- /dev/null
+++ b/BetterIServ.Backend/Controllers/UnitsController.cs
@@ -0,0 +1,61 @@
+using System.Text;
+using BetterIServ.Backend.Entities;
+using HtmlAgilityPack;
+using Microsoft.AspNetCore.Mvc;
+
+namespace BetterIServ.Backend.Controllers;
+
+[ApiController]
+[Route("units")]
+public class UnitsController : ControllerBase {
+
+ [HttpGet("substitution")]
+ public async Task> GetToday([FromQuery] string url) {
+ var client = new HttpClient();
+ var buffer = await client.GetByteArrayAsync(url);
+ var raw = Encoding.GetEncoding("ISO-8859-1").GetString(buffer);
+ var html = new HtmlDocument();
+ html.LoadHtml(raw);
+
+ var data = new UnitsData {
+ Notifications = new List(),
+ Substitutions = new List()
+ };
+
+ var info = html.DocumentNode.SelectSingleNode("//body/center[1]/table");
+ for (int i = 2; i < info.ChildNodes.Count; i++) {
+ var notification = info.ChildNodes[i];
+ if (notification.ChildNodes.Count == 2) {
+ data.Notifications.Add(
+ notification.ChildNodes[0].InnerText +
+ notification.ChildNodes[1].InnerText
+ );
+ }
+ else if (notification.ChildNodes.Count == 1) {
+ data.Notifications.Add(notification.ChildNodes[0].InnerText);
+ }
+ }
+
+ var substitutions = html.DocumentNode.SelectNodes("//body/center[1]")[0].ChildNodes[6];
+ for (int i = 4; i < substitutions.ChildNodes.Count; i++) {
+ var node = substitutions.ChildNodes[i];
+ 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,
+ Room = node.ChildNodes[6].InnerText,
+ Teacher = node.ChildNodes[7].InnerText,
+ Description = node.ChildNodes[9].InnerText
+ };
+
+ data.Substitutions.Add(substitution);
+ }
+
+ return data;
+ }
+
+}
\ No newline at end of file
diff --git a/BetterIServ.Backend/Entities/AuthKeys.cs b/BetterIServ.Backend/Entities/AuthKeys.cs
new file mode 100644
index 0000000..6852b9a
--- /dev/null
+++ b/BetterIServ.Backend/Entities/AuthKeys.cs
@@ -0,0 +1,9 @@
+namespace BetterIServ.Backend.Entities;
+
+public struct AuthKeys {
+ public string Session { get; set; }
+ public string Sat { get; set; }
+ public string AuthSid { get; set; }
+ public string SatId { get; set; }
+ public string AuthSession { get; set; }
+}
\ No newline at end of file
diff --git a/BetterIServ.Backend/Entities/Credentials.cs b/BetterIServ.Backend/Entities/Credentials.cs
index a561950..e01e215 100644
--- a/BetterIServ.Backend/Entities/Credentials.cs
+++ b/BetterIServ.Backend/Entities/Credentials.cs
@@ -1,8 +1,7 @@
namespace BetterIServ.Backend.Entities;
public class Credentials {
- public string? Domain { get; set; }
- public string? Username { get; set; }
- public string? Password { get; set; }
- public string? Token { get; set; }
-}
\ No newline at end of file
+ public required string Domain { get; set; }
+ public required string Username { get; set; }
+ public required string Password { get; set; }
+}
diff --git a/BetterIServ.Backend/Entities/MailData.cs b/BetterIServ.Backend/Entities/MailData.cs
index e174518..c12cfb9 100644
--- a/BetterIServ.Backend/Entities/MailData.cs
+++ b/BetterIServ.Backend/Entities/MailData.cs
@@ -2,8 +2,8 @@
public sealed class MailData : Credentials {
- public string? MailBody { get; set; }
- public string? Receiver { get; set; }
- public string? Subject { get; set; }
+ public required string MailBody { get; set; }
+ public required string Receiver { get; set; }
+ public required string Subject { get; set; }
}
\ No newline at end of file
diff --git a/BetterIServ.Backend/Entities/SingleResult.cs b/BetterIServ.Backend/Entities/SingleResult.cs
index 8f8a384..591840e 100644
--- a/BetterIServ.Backend/Entities/SingleResult.cs
+++ b/BetterIServ.Backend/Entities/SingleResult.cs
@@ -1,5 +1,5 @@
namespace BetterIServ.Backend.Entities;
-public class SingleResult {
+public struct SingleResult {
public TValue? Value { get; set; }
}
\ No newline at end of file
diff --git a/BetterIServ.Backend/Entities/Substitution.cs b/BetterIServ.Backend/Entities/Substitution.cs
new file mode 100644
index 0000000..e285a39
--- /dev/null
+++ b/BetterIServ.Backend/Entities/Substitution.cs
@@ -0,0 +1,12 @@
+namespace BetterIServ.Backend.Entities;
+
+public struct Substitution {
+ public string Class { get; set; }
+ public int[] Times { get; set; }
+ public string Type { get; set; }
+ public string Representative { get; set; }
+ public string Lesson { get; set; }
+ public string Room { get; set; }
+ public string Teacher { get; set; }
+ public string Description { get; set; }
+}
diff --git a/BetterIServ.Backend/Entities/UnitsData.cs b/BetterIServ.Backend/Entities/UnitsData.cs
new file mode 100644
index 0000000..2659ecc
--- /dev/null
+++ b/BetterIServ.Backend/Entities/UnitsData.cs
@@ -0,0 +1,6 @@
+namespace BetterIServ.Backend.Entities;
+
+public struct UnitsData {
+ public IList Notifications { get; set; }
+ public IList Substitutions { get; set; }
+}
\ No newline at end of file
diff --git a/BetterIServ.Backend/Program.cs b/BetterIServ.Backend/Program.cs
index ee1a01c..552506a 100644
--- a/BetterIServ.Backend/Program.cs
+++ b/BetterIServ.Backend/Program.cs
@@ -1,4 +1,6 @@
-using Microsoft.AspNetCore.Cors.Infrastructure;
+using PuppeteerSharp;
+
+await new BrowserFetcher().DownloadAsync(BrowserFetcher.DefaultChromiumRevision);
var builder = WebApplication.CreateBuilder(args);
diff --git a/BetterIServ.Mobile/src/app/api/iserv.service.ts b/BetterIServ.Mobile/src/app/api/iserv.service.ts
index 69f0bfa..1b928aa 100644
--- a/BetterIServ.Mobile/src/app/api/iserv.service.ts
+++ b/BetterIServ.Mobile/src/app/api/iserv.service.ts
@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import {HttpClient} from "@angular/common/http";
-import {Userdata} from "../entities/userdata";
+import {Userdata, AuthKeys} from "../entities/userdata";
import {firstValueFrom} from "rxjs";
@Injectable({
@@ -20,17 +20,14 @@ export class IServService {
public async login(email: string, password: string): Promise {
const split = email.split('@');
- this.userdata = {};
- this.userdata.username = split[0];
- this.userdata.domain = split[1];
- this.userdata.password = password;
-
- const data = new FormData();
- data.append("email", email);
- data.append("password", password);
+ this.userdata = {
+ username: split[0],
+ domain: split[1],
+ password
+ };
try {
- this.userdata.token = await firstValueFrom(this.client.post(`${this.backend}/login`, data, {responseType: "text"}));
+ await firstValueFrom(this.client.post(this.backend + "/auth/login", this.userdata));
localStorage.setItem("userdata", JSON.stringify(this.userdata));
return true;
}catch (error) {
@@ -38,4 +35,8 @@ export class IServService {
}
}
+ public async getKeys(): Promise {
+ return await firstValueFrom(this.client.post(this.backend + "/auth/login", this.userdata));
+ }
+
}
diff --git a/BetterIServ.Mobile/src/app/api/mail.service.ts b/BetterIServ.Mobile/src/app/api/mail.service.ts
index 49e71be..71ae917 100644
--- a/BetterIServ.Mobile/src/app/api/mail.service.ts
+++ b/BetterIServ.Mobile/src/app/api/mail.service.ts
@@ -37,7 +37,6 @@ export class MailService {
domain: this.iserv.userdata.domain,
username: this.iserv.userdata.username,
password: this.iserv.userdata.password,
- token: this.iserv.userdata.token,
subject, receiver, mailBody
};
await firstValueFrom(this.client.post(this.iserv.backend + "/mail/send", data));
diff --git a/BetterIServ.Mobile/src/app/api/units.service.ts b/BetterIServ.Mobile/src/app/api/units.service.ts
new file mode 100644
index 0000000..bbd6e1d
--- /dev/null
+++ b/BetterIServ.Mobile/src/app/api/units.service.ts
@@ -0,0 +1,27 @@
+import { Injectable } from '@angular/core';
+import {HttpClient} from "@angular/common/http";
+import {IServService} from "./iserv.service";
+import {UnitsData} from "../entities/substitution";
+import {firstValueFrom} from "rxjs";
+
+@Injectable({
+ providedIn: 'root'
+})
+export class UnitsService {
+
+ private schools: {[domain: string]: {today: string, tomorrow: 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"
+ }
+ }
+
+ constructor(private iserv: IServService, private client: HttpClient) {}
+
+ 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];
+ return await firstValueFrom(this.client.get(this.iserv.backend + "/units/substitution?url=" + url));
+ }
+
+}
diff --git a/BetterIServ.Mobile/src/app/app.routes.ts b/BetterIServ.Mobile/src/app/app.routes.ts
index 6c5d835..83cbf65 100644
--- a/BetterIServ.Mobile/src/app/app.routes.ts
+++ b/BetterIServ.Mobile/src/app/app.routes.ts
@@ -22,4 +22,8 @@ export const routes: Routes = [
path: 'mails',
loadComponent: () => import('./pages/mails/mails.page').then( m => m.MailsPage)
},
+ {
+ path: 'substitution',
+ loadComponent: () => import('./pages/substitution/substitution.page').then( m => m.SubstitutionPage)
+ },
];
diff --git a/BetterIServ.Mobile/src/app/entities/substitution.ts b/BetterIServ.Mobile/src/app/entities/substitution.ts
new file mode 100644
index 0000000..4360b79
--- /dev/null
+++ b/BetterIServ.Mobile/src/app/entities/substitution.ts
@@ -0,0 +1,15 @@
+export interface Substitution {
+ class: string;
+ times: number[];
+ type: string;
+ representative: string;
+ lesson: string;
+ room: string;
+ teacher: string;
+ description: string;
+}
+
+export interface UnitsData {
+ notifications: string[];
+ substitutions: Substitution[];
+}
diff --git a/BetterIServ.Mobile/src/app/entities/userdata.ts b/BetterIServ.Mobile/src/app/entities/userdata.ts
index 3c908a4..78853ea 100644
--- a/BetterIServ.Mobile/src/app/entities/userdata.ts
+++ b/BetterIServ.Mobile/src/app/entities/userdata.ts
@@ -1,6 +1,13 @@
export interface Userdata {
- domain?: string,
- username?: string,
- password?: string,
- token?: string
+ domain: string;
+ username: string;
+ password: string;
+}
+
+export interface AuthKeys {
+ session: string;
+ sat: string;
+ authSid: string;
+ satId: string;
+ authSession: string;
}
diff --git a/BetterIServ.Mobile/src/app/pages/files/files.page.scss b/BetterIServ.Mobile/src/app/pages/files/files.page.scss
index ed5dd56..d3f6835 100644
--- a/BetterIServ.Mobile/src/app/pages/files/files.page.scss
+++ b/BetterIServ.Mobile/src/app/pages/files/files.page.scss
@@ -2,7 +2,3 @@ ion-label {
margin-left: 10px;
cursor: pointer;
}
-
-.active {
- color: var(--ion-color-primary);
-}
diff --git a/BetterIServ.Mobile/src/app/pages/login/login.page.ts b/BetterIServ.Mobile/src/app/pages/login/login.page.ts
index 7178846..68b8c8f 100644
--- a/BetterIServ.Mobile/src/app/pages/login/login.page.ts
+++ b/BetterIServ.Mobile/src/app/pages/login/login.page.ts
@@ -33,8 +33,6 @@ export class LoginPage implements OnInit {
await alert.present();
}
-
- console.log(this.iservApi.userdata);
}
}
diff --git a/BetterIServ.Mobile/src/app/pages/mails/mails.page.scss b/BetterIServ.Mobile/src/app/pages/mails/mails.page.scss
index 5fb1bd7..27d19f0 100644
--- a/BetterIServ.Mobile/src/app/pages/mails/mails.page.scss
+++ b/BetterIServ.Mobile/src/app/pages/mails/mails.page.scss
@@ -1,7 +1,3 @@
-ion-select {
- padding-inline: 15px;
-}
-
.mail > div {
display: flex;
flex-direction: column;
diff --git a/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.html b/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.html
new file mode 100644
index 0000000..42f6107
--- /dev/null
+++ b/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.html
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+ Heute
+
+
+ Morgen
+
+
+
+
+
+
+
+
+ Vertretungsplan
+
+
+
+
+
+
+
+
+
+
+
+
+ {{subs.times.join(" - ")}}
+
+ {{subs.type}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Vertretungen
+
+
+
+
+
+ Nachrichten
+
+
+
+
+
+
diff --git a/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.scss b/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.scss
new file mode 100644
index 0000000..9930ba4
--- /dev/null
+++ b/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.scss
@@ -0,0 +1,48 @@
+.subs {
+ ion-card-content {
+ display: flex;
+ gap: 15px;
+ color: #FFF;
+
+ .times {
+ font-size: 25px;
+ line-height: 25px;
+ align-self: center;
+ }
+
+ div {
+ margin-left: auto;
+ display: flex;
+ flex-direction: column;
+ align-items: end;
+
+ .type {
+ font-size: 18px;
+ }
+ }
+ }
+
+ &.Raumtausch {
+ --background: var(--ion-color-success-shade);
+ }
+
+ &.bittebeachten {
+ --background: var(--ion-color-warning-shade);
+ }
+
+ &.Vertretung {
+ --background: var(--ion-color-primary-shade);
+ }
+
+ &.Entfall, &.Stillarbeit {
+ --background: var(--ion-color-danger-shade);
+ }
+
+ &.Verlegung {
+ --background: var(--ion-color-secondary-shade);
+ }
+
+ &.hide {
+ display: none;
+ }
+}
diff --git a/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.ts b/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.ts
new file mode 100644
index 0000000..1446b78
--- /dev/null
+++ b/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.ts
@@ -0,0 +1,51 @@
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { IonicModule } from '@ionic/angular';
+import {UnitsService} from "../../api/units.service";
+import {UnitsData} from "../../entities/substitution";
+
+@Component({
+ selector: 'app-substitution',
+ templateUrl: './substitution.page.html',
+ styleUrls: ['./substitution.page.scss'],
+ standalone: true,
+ imports: [IonicModule, CommonModule, FormsModule]
+})
+export class SubstitutionPage implements OnInit {
+
+ public data: UnitsData;
+ public showNews: boolean = false;
+ public currentClass: string;
+
+ constructor(private units: UnitsService) {
+ this.currentClass = localStorage.getItem("class");
+ }
+
+ async ngOnInit() {
+ this.data = await this.units.getSubstitutionPlan("today");
+ }
+
+ 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);
+ }
+
+}
diff --git a/BetterIServ.Mobile/src/global.scss b/BetterIServ.Mobile/src/global.scss
index 1e0fb47..3cc54b2 100644
--- a/BetterIServ.Mobile/src/global.scss
+++ b/BetterIServ.Mobile/src/global.scss
@@ -32,3 +32,11 @@ ion-menu-button {
.pointer {
cursor: pointer;
}
+
+ion-select {
+ padding-inline: 15px;
+}
+
+.active {
+ color: var(--ion-color-primary);
+}