From 80ff262122237e16ea14ed270e819a152ae77d77 Mon Sep 17 00:00:00 2001 From: LeChacal Date: Sat, 31 Jan 2026 08:54:43 +0100 Subject: [PATCH 01/65] chore: added `webuntis` package --- package-lock.json | 141 ++++++++++++++++++++++++++++++++++++++++++++-- package.json | 1 + 2 files changed, 138 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0847e542..87cab28a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -101,6 +101,7 @@ "skolengojs": "^1.1.10", "turboself-api": "^2.1.9", "typescript-eslint": "^8.49.0", + "webuntis": "^2.2.1", "zustand": "^5.0.9" }, "devDependencies": { @@ -3349,6 +3350,61 @@ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "license": "MIT" }, + "node_modules/@otplib/core": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/core/-/core-12.0.1.tgz", + "integrity": "sha512-4sGntwbA/AC+SbPhbsziRiD+jNDdIzsZ3JUyfZwjtKyc/wufl1pnSIaG4Uqx8ymPagujub0o92kgBnB89cuAMA==", + "license": "MIT", + "optional": true + }, + "node_modules/@otplib/plugin-crypto": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/plugin-crypto/-/plugin-crypto-12.0.1.tgz", + "integrity": "sha512-qPuhN3QrT7ZZLcLCyKOSNhuijUi9G5guMRVrxq63r9YNOxxQjPm59gVxLM+7xGnHnM6cimY57tuKsjK7y9LM1g==", + "deprecated": "Please upgrade to v13 of otplib. Refer to otplib docs for migration paths", + "license": "MIT", + "optional": true, + "dependencies": { + "@otplib/core": "^12.0.1" + } + }, + "node_modules/@otplib/plugin-thirty-two": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/plugin-thirty-two/-/plugin-thirty-two-12.0.1.tgz", + "integrity": "sha512-MtT+uqRso909UkbrrYpJ6XFjj9D+x2Py7KjTO9JDPhL0bJUYVu5kFP4TFZW4NFAywrAtFRxOVY261u0qwb93gA==", + "deprecated": "Please upgrade to v13 of otplib. Refer to otplib docs for migration paths", + "license": "MIT", + "optional": true, + "dependencies": { + "@otplib/core": "^12.0.1", + "thirty-two": "^1.0.2" + } + }, + "node_modules/@otplib/preset-default": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/preset-default/-/preset-default-12.0.1.tgz", + "integrity": "sha512-xf1v9oOJRyXfluBhMdpOkr+bsE+Irt+0D5uHtvg6x1eosfmHCsCC6ej/m7FXiWqdo0+ZUI6xSKDhJwc8yfiOPQ==", + "deprecated": "Please upgrade to v13 of otplib. Refer to otplib docs for migration paths", + "license": "MIT", + "optional": true, + "dependencies": { + "@otplib/core": "^12.0.1", + "@otplib/plugin-crypto": "^12.0.1", + "@otplib/plugin-thirty-two": "^12.0.1" + } + }, + "node_modules/@otplib/preset-v11": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/preset-v11/-/preset-v11-12.0.1.tgz", + "integrity": "sha512-9hSetMI7ECqbFiKICrNa4w70deTUfArtwXykPUvSHWOdzOlfa9ajglu7mNCntlvxycTiOAXkQGwjQCzzDEMRMg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@otplib/core": "^12.0.1", + "@otplib/plugin-crypto": "^12.0.1", + "@otplib/plugin-thirty-two": "^12.0.1" + } + }, "node_modules/@peculiar/asn1-ecc": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.5.0.tgz", @@ -5308,7 +5364,6 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, "license": "MIT" }, "node_modules/available-typed-arrays": { @@ -5326,6 +5381,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", + "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -6113,7 +6179,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -6642,7 +6707,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -8753,6 +8817,26 @@ "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", "license": "MIT" }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/fontfaceobserver": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz", @@ -8778,7 +8862,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", - "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -12867,6 +12950,18 @@ "node": ">=4" } }, + "node_modules/otplib": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/otplib/-/otplib-12.0.1.tgz", + "integrity": "sha512-xDGvUOQjop7RDgxTQ+o4pOol0/3xSZzawTiPKRrHnQWAy0WjhNs/5HdIDJCrqC4MBynmjXgULc6YfioaxZeFgg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@otplib/core": "^12.0.1", + "@otplib/preset-default": "^12.0.1", + "@otplib/preset-v11": "^12.0.1" + } + }, "node_modules/own-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", @@ -13395,6 +13490,12 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/psl": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", @@ -15693,6 +15794,15 @@ "node": ">=0.8" } }, + "node_modules/thirty-two": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", + "integrity": "sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==", + "optional": true, + "engines": { + "node": ">=0.2.6" + } + }, "node_modules/throat": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", @@ -16361,6 +16471,29 @@ "node": ">=12" } }, + "node_modules/webuntis": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/webuntis/-/webuntis-2.2.1.tgz", + "integrity": "sha512-9zDHBjcH52ps7FbQ/fO7tPg7WQbaqkXgRdvpogB1pCeV+EmFSbkQ2qOviwEYV/qzAKFVu58yq9wzdV/Atba80w==", + "license": "MIT", + "dependencies": { + "axios": "^1.6.7", + "date-fns": "^3.3.1" + }, + "optionalDependencies": { + "otplib": "^12" + } + }, + "node_modules/webuntis/node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/whatwg-encoding": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", diff --git a/package.json b/package.json index 3bf77641..e3ac657a 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,7 @@ "skolengojs": "^1.1.10", "turboself-api": "^2.1.9", "typescript-eslint": "^8.49.0", + "webuntis": "^2.2.1", "zustand": "^5.0.9" }, "devDependencies": { From 7a32ad096bcd3f5673d2335248550e9c4f544116 Mon Sep 17 00:00:00 2001 From: LeChacal Date: Sat, 31 Jan 2026 08:56:01 +0100 Subject: [PATCH 02/65] chore: create index.ts file --- services/webuntis/index.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 services/webuntis/index.ts diff --git a/services/webuntis/index.ts b/services/webuntis/index.ts new file mode 100644 index 00000000..e69de29b From aaee1a8d11830f727b55f74046f1dfdc0c75e344 Mon Sep 17 00:00:00 2001 From: LeChacal Date: Sat, 31 Jan 2026 08:58:13 +0100 Subject: [PATCH 03/65] chore: implemented `SchoolServicePlugin` --- services/webuntis/index.ts | 66 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/services/webuntis/index.ts b/services/webuntis/index.ts index e69de29b..50add8e6 100644 --- a/services/webuntis/index.ts +++ b/services/webuntis/index.ts @@ -0,0 +1,66 @@ +import { Client } from "@blockshub/blocksdirecte"; +import { User } from "appscho"; +import { Multi } from "esup-multi.js"; +import { Identification } from "ezly"; +import { SessionHandle } from "pawnote"; +import { Client } from "pawrd"; +import { Skolengo } from "skolengojs"; +import { Client } from "turboself-api"; + +import { Auth,Services } from "@/stores/account/types"; + +import { Alise } from "../alise"; +import { Appscho } from "../appscho"; +import { ARD } from "../ard"; +import { EcoleDirecte } from "../ecoledirecte"; +import { Izly } from "../izly"; +import { Lannion } from "../lannion"; +import { LannionClient } from "../lannion/module"; +import { Multi } from "../multi"; +import { Pronote } from "../pronote"; +import { Attendance } from "../shared/attendance"; +import { Balance } from "../shared/balance"; +import { Booking,BookingDay, CanteenHistoryItem, CanteenKind, CanteenMenu, QRCode } from "../shared/canteen"; +import { Chat, Message,Recipient } from "../shared/chat"; +import { Period, PeriodGrades } from "../shared/grade"; +import { Homework } from "../shared/homework"; +import { Kid } from "../shared/kid"; +import { News } from "../shared/news"; +import { Course, CourseDay,CourseResource } from "../shared/timetable"; +import { Capabilities, SchoolServicePlugin } from "../shared/types"; +import { Skolengo } from "../skolengo"; +import { TurboSelf } from "../turboself"; + +export class WebUntis implements SchoolServicePlugin { + displayName: string; + service: Services; + capabilities: Capabilities[]; + authData: Auth; + session: Client | Identification | Multi | SessionHandle | Skolengo | Client | Client | User | LannionClient | undefined; + refreshAccount: (credentials: Auth) => Promise; + getKids?: (() => Kid[]) | undefined; + getCanteenKind?: (() => CanteenKind) | undefined; + getHomeworks?: ((weekNumber: number) => Promise) | undefined; + getNews?: (() => Promise) | undefined; + getGradesForPeriod?: ((period: Period, kid?: Kid) => Promise) | undefined; + getGradesPeriods?: (() => Promise) | undefined; + getAttendanceForPeriod?: ((period: string) => Promise) | undefined; + getAttendancePeriods?: (() => Promise) | undefined; + getWeeklyCanteenMenu?: ((startDate: Date) => Promise) | undefined; + getChats?: (() => Promise) | undefined; + getChatRecipients?: ((chat: Chat) => Promise) | undefined; + getChatMessages?: ((chat: Chat) => Promise) | undefined; + getRecipientsAvailableForNewChat?: (() => Promise) | undefined; + getCourseResources?: ((course: Course) => Promise) | undefined; + getWeeklyTimetable?: ((weekNumber: number, date: Date) => Promise) | undefined; + sendMessageInChat?: ((chat: Chat, content: string) => Promise) | undefined; + setNewsAsAcknowledged?: ((news: News) => Promise) | undefined; + setHomeworkCompletion?: ((homework: Homework, state?: boolean) => Promise) | undefined; + createMail?: ((subject: string, content: string, recipients: Recipient[], cc?: Recipient[], bcc?: Recipient[]) => Promise) | undefined; + getCanteenBalances?: (() => Promise) | undefined; + getCanteenTransactionsHistory?: (() => Promise) | undefined; + getCanteenQRCodes?: (() => Promise) | undefined; + getCanteenBookingWeek?: ((weekNumber: number) => Promise) | undefined; + setMealAsBooked?: ((meal: Booking, booked?: boolean) => Promise) | undefined; + +} \ No newline at end of file From af52a24a02d64e315a603d4255f2408bb8f3b601 Mon Sep 17 00:00:00 2001 From: LeChacal Date: Sat, 31 Jan 2026 09:00:11 +0100 Subject: [PATCH 04/65] chore: added `WEBUNTIS` to types --- stores/account/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/stores/account/types.ts b/stores/account/types.ts index fa79ea34..fc9a831a 100644 --- a/stores/account/types.ts +++ b/stores/account/types.ts @@ -92,6 +92,7 @@ export enum Services { PRONOTE, SKOLENGO, ECOLEDIRECTE, + WEBUNTIS, TURBOSELF, ARD, IZLY, From 22674b857d9be7268ce5f6859d73c52af8911467 Mon Sep 17 00:00:00 2001 From: LeChacal Date: Sat, 31 Jan 2026 09:26:32 +0100 Subject: [PATCH 05/65] chore: created refresh.ts file --- services/webuntis/refresh.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 services/webuntis/refresh.ts diff --git a/services/webuntis/refresh.ts b/services/webuntis/refresh.ts new file mode 100644 index 00000000..e69de29b From 7da3c5ecc8619050eb92844eb3e8874a08f2f1f1 Mon Sep 17 00:00:00 2001 From: LeChacal Date: Sat, 31 Jan 2026 09:27:05 +0100 Subject: [PATCH 06/65] chore: added `WebUntis` to `SchoolServicePlugin` --- services/shared/types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/shared/types.ts b/services/shared/types.ts index 974a2aff..5fca4868 100644 --- a/services/shared/types.ts +++ b/services/shared/types.ts @@ -6,6 +6,7 @@ import { SessionHandle } from "pawnote"; import { Client as ArdClient } from "pawrd"; import { Skolengo as SkolengoSession } from "skolengojs"; import { Client as TurboselfClient } from "turboself-api"; +import { WebUntis } from "webuntis"; import { Appscho } from "@/services/appscho"; import { Lannion } from "@/services/lannion"; @@ -58,6 +59,7 @@ export interface SchoolServicePlugin { | TurboselfClient | User | LannionClient + | WebUntis | undefined; refreshAccount: ( From 46d1fff88f8352ae694938a59b81a590dbfe790e Mon Sep 17 00:00:00 2001 From: LeChacal Date: Sat, 31 Jan 2026 09:27:56 +0100 Subject: [PATCH 07/65] chore: renamed `WebUntis` to `WebUntisService` --- services/webuntis/index.ts | 38 ++++++++++++-------------------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/services/webuntis/index.ts b/services/webuntis/index.ts index 50add8e6..24d2b48a 100644 --- a/services/webuntis/index.ts +++ b/services/webuntis/index.ts @@ -1,23 +1,7 @@ -import { Client } from "@blockshub/blocksdirecte"; -import { User } from "appscho"; -import { Multi } from "esup-multi.js"; -import { Identification } from "ezly"; -import { SessionHandle } from "pawnote"; -import { Client } from "pawrd"; -import { Skolengo } from "skolengojs"; -import { Client } from "turboself-api"; +import { WebUntis } from "webuntis"; import { Auth,Services } from "@/stores/account/types"; -import { Alise } from "../alise"; -import { Appscho } from "../appscho"; -import { ARD } from "../ard"; -import { EcoleDirecte } from "../ecoledirecte"; -import { Izly } from "../izly"; -import { Lannion } from "../lannion"; -import { LannionClient } from "../lannion/module"; -import { Multi } from "../multi"; -import { Pronote } from "../pronote"; import { Attendance } from "../shared/attendance"; import { Balance } from "../shared/balance"; import { Booking,BookingDay, CanteenHistoryItem, CanteenKind, CanteenMenu, QRCode } from "../shared/canteen"; @@ -28,16 +12,18 @@ import { Kid } from "../shared/kid"; import { News } from "../shared/news"; import { Course, CourseDay,CourseResource } from "../shared/timetable"; import { Capabilities, SchoolServicePlugin } from "../shared/types"; -import { Skolengo } from "../skolengo"; -import { TurboSelf } from "../turboself"; -export class WebUntis implements SchoolServicePlugin { - displayName: string; - service: Services; - capabilities: Capabilities[]; - authData: Auth; - session: Client | Identification | Multi | SessionHandle | Skolengo | Client | Client | User | LannionClient | undefined; - refreshAccount: (credentials: Auth) => Promise; +export class WebUntisService implements SchoolServicePlugin { + displayName = "WebUntis"; + service = Services.WEBUNTIS; + capabilities: Capabilities[] = [Capabilities.REFRESH]; + session: WebUntis | undefined = undefined; + authData: Auth = {}; + + async refreshAccount(credentials: Auth): Promise { + + } + getKids?: (() => Kid[]) | undefined; getCanteenKind?: (() => CanteenKind) | undefined; getHomeworks?: ((weekNumber: number) => Promise) | undefined; From 87a5419a4cbdbe2fb26c8a86861d339637bf2eba Mon Sep 17 00:00:00 2001 From: LeChacal Date: Sat, 31 Jan 2026 09:48:43 +0100 Subject: [PATCH 08/65] fix: resolved packages conflicts --- package-lock.json | 63 +++++++++++++++++++++++------------------------ package.json | 4 +-- 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/package-lock.json b/package-lock.json index 87cab28a..a129ac9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -90,14 +90,14 @@ "react-native-linear-gradient": "^2.8.3", "react-native-mmkv": "^3.3.3", "react-native-qrcode-svg": "^6.3.15", - "react-native-reanimated": "~4.1.1", + "react-native-reanimated": "^4.2.1", "react-native-safe-area-context": "~5.6.0", "react-native-screen-transitions": "^2.0.3", "react-native-screens": "~4.16.0", "react-native-svg": "15.12.1", "react-native-web": "^0.21.2", "react-native-webview": "13.15.0", - "react-native-worklets": "0.5.1", + "react-native-worklets": "^0.7.2", "skolengojs": "^1.1.10", "turboself-api": "^2.1.9", "typescript-eslint": "^8.49.0", @@ -14034,25 +14034,24 @@ } }, "node_modules/react-native-reanimated": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.6.tgz", - "integrity": "sha512-F+ZJBYiok/6Jzp1re75F/9aLzkgoQCOh4yxrnwATa8392RvM3kx+fiXXFvwcgE59v48lMwd9q0nzF1oJLXpfxQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.2.1.tgz", + "integrity": "sha512-/NcHnZMyOvsD/wYXug/YqSKw90P9edN0kEPL5lP4PFf1aQ4F1V7MKe/E0tvfkXKIajy3Qocp5EiEnlcrK/+BZg==", "license": "MIT", "dependencies": { - "react-native-is-edge-to-edge": "^1.2.1", - "semver": "7.7.2" + "react-native-is-edge-to-edge": "1.2.1", + "semver": "7.7.3" }, "peerDependencies": { - "@babel/core": "^7.0.0-0", "react": "*", "react-native": "*", - "react-native-worklets": ">=0.5.0" + "react-native-worklets": ">=0.7.0" } }, "node_modules/react-native-reanimated/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -14167,33 +14166,33 @@ } }, "node_modules/react-native-worklets": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.5.1.tgz", - "integrity": "sha512-lJG6Uk9YuojjEX/tQrCbcbmpdLCSFxDK1rJlkDhgqkVi1KZzG7cdcBFQRqyNOOzR9Y0CXNuldmtWTGOyM0k0+w==", - "license": "MIT", - "dependencies": { - "@babel/plugin-transform-arrow-functions": "^7.0.0-0", - "@babel/plugin-transform-class-properties": "^7.0.0-0", - "@babel/plugin-transform-classes": "^7.0.0-0", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0", - "@babel/plugin-transform-optional-chaining": "^7.0.0-0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", - "@babel/plugin-transform-template-literals": "^7.0.0-0", - "@babel/plugin-transform-unicode-regex": "^7.0.0-0", - "@babel/preset-typescript": "^7.16.7", - "convert-source-map": "^2.0.0", - "semver": "7.7.2" + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.7.2.tgz", + "integrity": "sha512-DuLu1kMV/Uyl9pQHp3hehAlThoLw7Yk2FwRTpzASOmI+cd4845FWn3m2bk9MnjUw8FBRIyhwLqYm2AJaXDXsog==", + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-arrow-functions": "7.27.1", + "@babel/plugin-transform-class-properties": "7.27.1", + "@babel/plugin-transform-classes": "7.28.4", + "@babel/plugin-transform-nullish-coalescing-operator": "7.27.1", + "@babel/plugin-transform-optional-chaining": "7.27.1", + "@babel/plugin-transform-shorthand-properties": "7.27.1", + "@babel/plugin-transform-template-literals": "7.27.1", + "@babel/plugin-transform-unicode-regex": "7.27.1", + "@babel/preset-typescript": "7.27.1", + "convert-source-map": "2.0.0", + "semver": "7.7.3" }, "peerDependencies": { - "@babel/core": "^7.0.0-0", + "@babel/core": "*", "react": "*", "react-native": "*" } }, "node_modules/react-native-worklets/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" diff --git a/package.json b/package.json index e3ac657a..440b8700 100644 --- a/package.json +++ b/package.json @@ -96,14 +96,14 @@ "react-native-linear-gradient": "^2.8.3", "react-native-mmkv": "^3.3.3", "react-native-qrcode-svg": "^6.3.15", - "react-native-reanimated": "~4.1.1", + "react-native-reanimated": "^4.2.1", "react-native-safe-area-context": "~5.6.0", "react-native-screen-transitions": "^2.0.3", "react-native-screens": "~4.16.0", "react-native-svg": "15.12.1", "react-native-web": "^0.21.2", "react-native-webview": "13.15.0", - "react-native-worklets": "0.5.1", + "react-native-worklets": "^0.7.2", "skolengojs": "^1.1.10", "turboself-api": "^2.1.9", "typescript-eslint": "^8.49.0", From 9e398b75ae2f2162bbdfb6a24de8031faac8146b Mon Sep 17 00:00:00 2001 From: LeChacal Date: Sat, 31 Jan 2026 10:06:54 +0100 Subject: [PATCH 09/65] chore: edited refresh.ts for webuntis --- services/webuntis/refresh.ts | 44 ++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/services/webuntis/refresh.ts b/services/webuntis/refresh.ts index e69de29b..ac086062 100644 --- a/services/webuntis/refresh.ts +++ b/services/webuntis/refresh.ts @@ -0,0 +1,44 @@ +import { WebUntis } from "webuntis"; + +import { useAccountStore } from "@/stores/account"; +import { Auth } from "@/stores/account/types"; + +/** + * Refreshes the WebUntis account credentials using the provided authentication data. + * @param credentials + * @returns {Promise} A promise that resolves to the updated authentication data. + */ +export async function refreshWebUntisAccount( + accountId: string, + credentials: Auth +): Promise<{ auth: Auth, session: WebUntis }> { + + const school = String(credentials.additionals?.["school"] || ""); + const username = String(credentials.additionals?.["username"] || ""); + const password = String(credentials.additionals?.["password"] || credentials.refreshToken || ""); + const baseURL = String(credentials.additionals?.["baseURL"] || ""); + + const client = new WebUntis( + school, + username, + password, + baseURL + ); + + await client.login(); + + const auth: Auth = { + accessToken: client.sessionInformation?.sessionId || "", + refreshToken: password, + additionals: { + school: school, + username: username, + baseURL: baseURL, + password: password, + }, + } + + useAccountStore.getState().updateServiceAuthData(accountId, auth); + + return { auth: auth, session: client }; +} \ No newline at end of file From 92c1bf3a7b9b6159eb4f0e7d9f5e2473cb5ca86e Mon Sep 17 00:00:00 2001 From: LeChacal Date: Sat, 31 Jan 2026 12:07:58 +0100 Subject: [PATCH 10/65] feat: added Web Untis to `Supported Services` --- app/(onboarding)/utils/constants.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/(onboarding)/utils/constants.tsx b/app/(onboarding)/utils/constants.tsx index a7c58faa..82fcd3f0 100644 --- a/app/(onboarding)/utils/constants.tsx +++ b/app/(onboarding)/utils/constants.tsx @@ -58,6 +58,17 @@ export function GetSupportedServices(redirect: (path: { pathname: string, option variant: 'service' as const, color: 'light' as const, }, + { + name: "webuntis", + title: "Web Untis", + type: "main", + image: require("@/assets/images/service_webuntis.png"), + onPress: () => { + redirect({ pathname: './webuntis/method', options: { service: Services.WEBUNTIS } }); + }, + variant: 'service' as const, + color: 'light' as const, + }, { name: "separator", title: "separator", From c99814121e1d3b54773d6ab74f9a80605b7613a3 Mon Sep 17 00:00:00 2001 From: LeChacal Date: Sat, 31 Jan 2026 12:08:21 +0100 Subject: [PATCH 11/65] feat: added Web Untis logo --- assets/images/service_webuntis.png | Bin 0 -> 24923 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/images/service_webuntis.png diff --git a/assets/images/service_webuntis.png b/assets/images/service_webuntis.png new file mode 100644 index 0000000000000000000000000000000000000000..3a98819a08bbec530c3b0fd82d5ff6db18efd786 GIT binary patch literal 24923 zcmZsCby!qU_wLLvbf=Vnv{C~|r=lQ@(%mH;QW8T-gCO04(%n6Dmq<70fOLc8J^1^+ zd!Ogt`xmo@*=xtyd+m3<>pfvAN;0@ulvp4T2v_#at9Kv}1o#L6VLbf#NGVkc0ug{@ zU%gQC0Pkj?w~$QD^=8d2Y`xBVLt3Fpuv^&|KpAhf%Q5{57joVm=j|5w!}cw4vq@O* za&u@n>l@O~%s)Q1I}Z7d&9&;;&++iO4}G!pbGg_bZOL!B=RFa(98F1ydUZc56M+#5 z#|V|gzbEBXaS!)=Z}wdp|9vpp^TuMCDdOH z!1uEc-?yRn`wZ6Dy_ST&$_|G^v$0-z+zqG_5`YOqfo9!8 zTlx5&+rUdlc`QryL0>I%mK`iJem}s&5SmD8Z5A^jDnd1QXqbHz*5W&Zv z*HV*$2{G9G&3xrSai1r=9^7LHf-xl#R9gFC4-R>N-Z43LI{cjEPPIMu$(Xn;c z8Im6>aH>*NtWC?9XJuPNy1u6EhOkPG17{J322u- z=qk6Wgt4H!ueo-2%!tFKI^NO0R7(3XD+aqpf0ud$52UHR=;_GTgT~xi_a=UFstxF4II!Y;9d%u)~tj34D<69!ENLJ_)49b2DB?6X> z5l$4TehjIoC?v5Eeq?VzWojK2)F4~>oPN~@LIgUf|NMRy1W8A8^nr}W*5DRY#f3|b z0&DLaqJ#Q7P|}k^ka)H1S5;JHjWDX?C*{K9HwgO)B2aUl*`_=Mmk<>F_=pjaWMBM) z-Y5bNMUXL5{7nF%X8+hDkA))2OUt|#nCQS2@^Wr>42TXYEco@4Y{kkaboYNtDI?&D zzzEW^&5+6vT#By3l2=l3*Tl*1A3p=0jS8RpWx8Fzc{Z*rmH3b2*%)A3``Xri`7qtf z0cD>Ic3+Bc7jvum2DxH{WHQr%4_!|RoN<{DbOv<;=FDZpP3+18ujpogR*lU2OULAF zY|n;$s>_;(X-=cs)HE7Ol6HbPhlbu)6nXASywqKtZO)@$`3Ib zn%C|gay`3F6yk>-Dn$<`vNv7F)yUC93 zV2P!0`Gao4{A?f1S6C`)kv`F{7L+igtwJNH^;(ms)tg$FB z!Kd{Ws^Jvc$I7OqBOMPBNlEwn31hfh#(0sPaP{oD1xMw~6C-@?^N~J>-TvlgtWa@Y zjuoF)Ei$(S+R}wN=dafCu3Kc#sruigkvL#Xcn(3hL~lTrUw4tYTgK8YsK$Qh+7Z}` zZ!Knir@B!KUtFt8o@Trp6`~nhHg2;gMJfa;Zlso+3LQWfH4w|0Yy9rtP6)s>v)C4< zosZm#f0khjdiK+q2>4&@3I^{8N4p$$V}Pm;GY$D|Uad4Agxk5-Sw|O|a=Q?*OElZg zsL!bb!9>j;qo=(}@$%TNMAvR;Zk^~0KR;4~Nu?>)7Ng`lfe0gxUV?WzAVVintwc<#idrRFy9kn2zklLx!n_n?~bYe zCE0?uZc$j!@4!@1b`Jfav9R1qEBjKufwt%GO=ze!w9;*x zXJ%BJ^p-I+Fx@d*B^il2GTV9b?cJVrjZ2(M@oJm&Y4LZ^1A9v2%+I#@9CJlkLRPrppQ{yv_!yq_aj4+!0C0 zxJp2q!q|AP(n>f}-LsW4Vz;Hf?^?5ius-R1PNeZO>35foL~s@{kyGjX%sN=Pu3Zzv zbvm&&GC&%*&uYkOf8#fQ#J1SDetRB?4aAefgX*=#*8<;77;91cRi)pO2te}p<%z^@ zOMMmdeFG_ojsL1_ZeM@p8wJ8aIxn^~mW)8K6HRh;$9I`ssm&$zG#1%?B4n&ez*jBH z=$X9+%>czS%|FQUD}|SJ(h!0wv~stk4DoA$U_Kjb;RI2K0;T;l{KUr8>Z%`3K0+wF z^a&d#Jei128g!H5^SWd66+;2PIeJnp224q#H2x@45)jbgUR{=97Ov&G$8+2$XqrBw zY^=PJX65xt4XUm9ZIqVliKbFoUqrAhsAcw9-S$sMhWGH>w9dLeXi6MB-OpPV^^k~; zJ;iVKWlp~cQig^;IsY-(s}i)X+#B7hQ{AhTZ370(X!NpH2SQMUEZbv8H5zgDK8yY< zJ3FsSv{9#gH+$`q z-loKmcKkp^$~lTmUHqwDBOj!o-V@ZC?A0$y75a6_JLc@R9q5)}M=El}wsd2Ttu`LT z2zueLR&(c+cfAl(Y2u_KWjcOMZ~_PSQl9j%__>b14qo~;_=re&wxF<4KQ|lZLpnTS zco2w{w5&!3W&!r12THy>7g5Jeikk!0&U&+Dmo{v&k&_c;$&ALoPw3Yv)W!a7yCjz6 zW~N1A+uipooA1#*%EZ80`@o*)0rNG^opDKda=hbicl$1x7TCtCTk9{#sQH}5om66M z%QRGaE(8fUbn>~%Z5eufLw|fAf}fVWG-b4O>wM~icC~{E~maUzkR}aG5_i}m&5ZkmiJG)3)`DN#vEw z<{|WKW*eS*6VlHGy1Szxgx-Y=oeGK?y;U>$?zbm;yO%_<8q^*E=EnCZd}bC1pgk%z zpJWzPqcdmkztN?@)>=T(cRTz9s(8AnKeq7*oGn+o6Pa-}K5;s9k!3muvASUqds?nz z)?`2gS0Fz7;LGoT#XA-vLZ0q%8eF{(Md%laf+~kGwbSta$XjgDp7^Kbo8*n@kof}0 z=+I`!ylq*dyF<~qc|StK?$(J&g{(Py@+sGMXYLQXHDA!?T}aBTT^K;N%{AI7Y3&vW zuBwdb#}sA(z(|QnP~y>>Td5i9TvWCC_K^a86!4b)W^=W{*J467>q0Zz>Y)jXZjT%b z>%Z+|E#0UCV4Dhvk5VuE)S z&$M~dUpBEkN1Zny@5z0kq5|{l?-kQ_mDgbL2G#JK^dck%t!c5WiIChE8`Ef;J+p&9 z4nT_;yN$GCMo^n|5w-dkNQE&+oA*MmqH!W2oS9>o?ql?`%_4F%xoC)x+%|n!?hUUI zHQ$Cq^WLW#Wxw2H;7sFy@GZ!Q;6`;efh|?^$>)!KGZV$*J6}4^)3xdwaFBzChSI0t z5S6!Uf8x_v37$$iG35D-WCb6HA9biH>d3sbi{;p40?pCyY&JLYW=_fN zfF-*3Ya`|}?Is!t?+D9-j!?G4JsI&WovP|#8Xt&qT=`cYbI;?E%sHMl?8pWpK zt#;-YwTT?h+t0hSW1$N*a!@+(xKeB=NF-7oX*zseTUV+TIcC@;HM7xHeAMBhC73_0 zG3L8A5I$)mOu@Y;`iM)DGymDA*6WzN2=x1q>++sHF5>~w!^nK$jy$fjA;T%yX^uFM z6ZtUf5Hk>z!g3|!+j~<J*gNn;!AXp}+A5LF*ktlS-~j=8yk@tTajIH?)BW*EsH z?3}B(adFimV?`02Ns%CSkO(H!G(I#(zOoy|uejMjJsso&?@^D2RdWq6L>c#Zz7u$Q zoMtiQJe(4eAg{?}8+5w$$Lw>ucb(&ah z(Nv3Sz3mhu7EPomqogD zncp^ccR?9mZwD(XB-wnMv_shamkYR9cqACrYOf7&xTV@JumdpCbH6n z93-{JeoCLvn2J_V>HNm8yV>hEhyVj1O%-A$h`GBR z?U}I5k>zzUJiWVhyrQeI=fbU6AtAi}o>Ny6eYfO3*O_BZRo%7c@-vYSNSo+mQGkwn zn2b&(&h-wZK7@&qe*;fFxAl$%&BDLHuKFfx-$zWMeZ6>c(dyP-xV8wG>tafkne&nDO&+_h`}QbJgPxV-yxXSK+#PFORc zrSeQteSs0APDgaJkz(OJU5{+=gEY?;jV`Q}*hpSS>IV@FX*>`Cx)|MGCD8SCYnH1x z=khryvv*G8xPMf`<31ao#$4>|y}I{cUf8!SBiwE)9z|IfJ>`U?Fe0R?4%Sv3*;tls zk@C2Lf8IT}$NFBI!f0k8jPmBk>Tdr*!y~WVR5O>jDK+e9)DLqs3Yp!niUInAns^mF zwL_QAk5O9ZmRL8_F>9fFsRT+F`m?M9iA`VHHfPn(@^*>iNXHc1kgM6l*{_mScwie4 zV6alpUd1CN3ON5`>b&r@GTo>%n-Aqm)Y7-^4@a|-?{lc<_U*^9R7=@j4MC3ub6ORY zx@|gKN08!o`q)@duHV>_)AZEVtk~MQ8k;6(&k(-J(cp~oIpDz2LaV^_qmhFE*XUSHgg(_(|vACsboCB-9d+ok3_sUPHROnl! zW3=b-jsPvF3Cc<;f+_h-I2-MYU8f9}$u9x{{F$ZeH#a#FsrA!ZDsqGKtdKJd*pAH6P}#gj*xxE`^&96h0y<1=r}G zJ3Br}T^crLfxKWsY_)%o;SFt);_BlZO%3RC=&5laz1_=>2hHY?d|cj4zJNKiY6$-X zDKXQ!#d(_8$ud~6|G=}I3rYuF>+06_6`}xuI9hf%Ga!GzTbbDSY1Lc+i$Wbtog&I} z-eKBspLPA$&x(TyEY(#_`~d5+wMrYD)4Akvyb5& zNI%0OOV^6MYlpS@6_X+&cLTmb?Y<@?bn#PPiw9p6)+ImwZBrP<5jDDbK2p}ADJ(xd zn#XF9ghd~1vd_sQWT$|HA_5+Rk-W8n(RX}vsYX)aVAi~?(7z?&%$msM%1%{>jaioi zMc7@xLlImu;}pCe)g~L!ZKnS1su5Z88i5Zb@AkA<joP}|M!Rk-X)^Ejs|(aPI8 zfa}_+VoKW4M7L=Z-iYGHECn283CUU7~vl&jEs z+rU?J=Nr+VZk89BRQ1*ol~qw5T<7paz9G`v$By!5&%ke9^-KsyqjF@#e3mkyTxB#* z!xOZlb6dookYgDzV}pR{2>2?m5gUii5pRFY`b!`Qqm);t{L*AaxL+j(TnS?CfuktYD)EKonBBhf7pE_* z=6e09zU_=u&#jo9as_{FM{z7vU;-6x2`)(EYSj)$qPE_%4+;pqS@3yuGQ40fB9B5X zi5+J>>;1>2z;jlrxDM~epqhv)8a8IJDGPsSZw<7!1_>=F;A*u~mW$f@%7z3coD;jR zkXq^mpX+}EpQjA`@Pi6UHk~XBa-54$oZF1~PGNT)9?7#VDZ1jx*ceYaCTx?!ewu%&NNN9Ar>3y)qV!9KJx83R-);N0~A)vVO#>WqR)iy;A zWNYkf(Y@qqT9{Jw6({drXn@kwg@+<_z6kt^Hc$pG^T{V)gT1#uhx{Zn>`wUku1mGu zQ*cGp;O8e7&beUa^@L9q_O+#Xyev}fMwSs@B5!07)Ff1g14CMMh(r|m&5golg;Rm) zZE^}Miu-tw8TB6|PGfVDUH3J^(Tz%pL8Yf%+ZYPUph=yHt8Agz{Jw@-S6;qq<434?8aIvrT)WnJc=;ulaA3cE} zM3nWN6(}Z@g|F-7f@Uk>nNY>|G zFowEC9^?#alqTGe=xV<=wzu{?&=&QII6tGbI_36;7JxjHKPsaG_j5ZY z>$g;Oir%SC(0YY|h6&%kr4gYwsWu;HbAO;`O-thd4*nILG=j~62vlAZwEg6vQXmA7 zxW1^{`#T1)oU=U?A4fI}fjsmX(ZumKDM8^S4; z2I#L8439`4A2r4Oy8W*jQ}#fOzt;KvV8HtxjA^=OZytJXc?}TorROSk{~TIAIFu?4 z0E|vKL@`j6Xqo3J`d7y>O95`VA$UUTLrKLW0q|bTk?V!}2`)+qjsbXHj~Zh&j)(e1 zKRoD1X>J6CikQFedsiaZ=^(+7|G%a(SMF|f+%z%uUZYNazU)coHW61#%KBN2Q zjQF8q!4A-ZW|$?w(F2^61LX^k)?hV>dm@#Q*N6?MC>Ll#PECWJ@=Q=h{onf45g7Ha zL(0)P$FL=@?PnXg>b8xZ!%ZqsE5skFDDVpmm;(2yfSv#Jo{E|YBKB8zgeK zaPjiKgB(VJ^6Be}(Cl|~dP0|%tDgFN-ea~~MbDYfz4 zU>Y=rB}vl5B)H4yx;#p-+k~R@Cx%UW_1DmZi#rM5OY`8Q%8>WQ=db|WbSrgX%QH~ITieOUBKx)GeO2^U3xs{?=6Po=RR5Ut z#dO@*J2D0zqK8G%BQ!cR-g5W}6*=$Ko>?9I_yW8&`xrG?osZQzvNZ;}(kg7VrVg%P zKfNNYdvDlNiEK}_tMHT1b4KTEBu1xku${%Bb}zQh9edbiv0@M$K^&10-JCJwhD!}S z@ov|1igNOXZIVF)+x$$Un7UCf2k)~yP^@in-Rw?`rKdkj8hYY*j3In?rb(MARYvn(%;^uoFF8(R7V~H96{C45P^eJ3X zCwgR-KHWLym5N$i#J374djwOJ9(4V+%obk8;8V#ZvG|;fzUPKag3)81`e?UB{&zIl zQM6WDzB z*RSudz;6?w9Ub20E?M71gM9I}Yq>|qz-cy1PdEG1#(>Pgil=#mcaZFVrwd!n%tN^? z+)*f8g}cQgnW?|k4gb{uCSXh`V5zBBy?SKv9lfmY_eF9uy09%a8zi$WWes~KWnP(_ zPJaJ((MFj9s72s^x&>c9j=GH=xT?D6ws1|5lNzwDOhAvJK*vp2h6|*=c*{?_vzpz@ z6GaGH>*RTqOzjqj$w0kjsq%jMS>4DK6?#P7(p;cObsrSodRAKBk%``Hq9;PAT;F_P z@*N!wuWR6T!bM<7Oob}8)d8mu{eF5e-gr#ZR3rHHY&k97-D5%LxJreS&u@W3(m19k zNNW+}(+$n_jWDhFb)7;ula$1DcE-o``bVj3S992F{rG);lv}EkWUHBo)+YbIX2Oj# zP`dYMe)As&lhZVykDI=(%t`l-MJ$OS#0eLYIWj7FAgP3ePVFn^hUlglDNM%N7Qem_ zzbuipa2Zv!FX0F8JAsZr%<{(Wxxpz~EEJtLs{G`i(1)PbJ?gqm(M%h^Ux*lq(l^`c zFC^&&7%Kj~4sm;|v~-8#WYD-D&3WYfP3+t^^@Z6bl=_Xhg^|mu@94X$9PH4uAJOKC zwmSBx*E0~aLOk4oL{J)YyJW=^09_14v+Nj63Z`4&+~N%Kp3k*1Vifa4LeHN>i{Nx0{tI%U><*hdx`u@&a0!togZlD>LVCB#{14VQW zGI?w(Eq>$!@|+Wkuio)uGU-}VDKx8U;2B2;BSVBN|C5TS5JVqNla>S<7wGimSYQH`&JE;GJqRtdjTsD(60?S)8O*UW`5vKQ}tHG#7J2Lr^;9 zpuNHxwjHQK1xhcX&cq>j2zh)XB%|#jVvoW=$Hi+^OV5jivk?WI+D5vyG1sgC2S};t zV>Kb71#?|HF3FAG{~mvIyc|GUXY`+5YQ1ixs9)XcL7DLf7#eN|w1zlvlrcv+h=^=o z7Furw&tOn+6VOR~O@GUy|z7`OjGF%v98gMUG^n~9<*mbHCz55+|i@*jr~{e|s`uqOwH z_WJE2;uJ~2w*lxw#xQ=>vKaO1`vDQD#bW7O9Qv*X7_xnN?0@$!x_V+kVM>6q9{jSoHB% zqwk{tC5q&nbmT2;yF#E^zfh=eD#)%P5OR%lU_;Y#4^8t5aa09qA zl*+ISVDo~~0I>Sunl0x6bzVLgD|`9#4-z7{4S=*~w!WwUIKBiLuwDM=+kyW~X&abQ0S8(ks`qf;Qljg zAEs1a-tsRS$OAmcPFMy0lP?AJ0J=a~KP zpf4Y+xRAuO2Y)I_z$xV<^!fKRV}Ymn)5fY4SW^OjN5BZm&G`N8VX{Ag^)C4IitWLf z9GxwY{&Y^SAOF*r{eJ~z@xr%p)>N)mL_T$Aaci~htT~RR`4)?va_-otf zPCXbnl>T?_8(i)iaUzHL3ywz|g7*iP8@PhkI_V!c0QFElUgyBA)B3l|r7XuqTBqH{ z%d@9~>t@t9BGiu5=OSo6crpi4l{;;-=Cro$EMEH|cJ6>4U~3%Lu8uoyvZ-_;jZD`+ zG_B|KyR6Ie@IQ)g8FB?sgN?en&qCCrlcHX5$987EF34l~Zv#~-s7qM7?)KVhq|UDzcI%1yvceY*_>ayx3z zR7_PI4IFJ8Jsd+ElYCqr@Lu|k<6V^CJ+t1WAM+yj7WXMPhzEyK}fk;L8oD2OM>PZqt9W5;4!%^`g8>Oz0qRCD(yy2wQj4f@xMjx87R zbCCyGlaz9|G0R&2q|>mr;GO8rzDc?Vrqbh3L7BlW+VU8u8}vI@HqJ)eTl6&ZRT#rF z&81H^cfmU}+yqaBZV=^~%a;7Olw^}>9hWC|%xl5-uoM`Z#hX@Rq@L0h_7#nP=T}=E z&1GQU3$Y*qn=YOR{^22(_Q=cf z5XOizt|?~3&;7I86fY2;xR+tTQvm+tuB2G;?*A;0OqMld{z-e^-EGA^_v|$W@jm$} z`4u^c0*wNPf{22gf@V(t`!5*IJAHD|>fxu?5CkusTPyExwC3%fc5Vw1;fnVeEOJbW z;|&z7OF!+%x-;(*;|tHNR!|=|Q!O#CXEsx{&Y3%$3S%sio(ug*m%7;&Q1|MRHc`!R z^nV+?gby>7ODU>82EtuG7x?_v^QWOJuTsOfE1PG93*FlIO4>fsw-25J2m7Pp;NcAJ zuJY=+&MmvD&mCC`TBslkK`8PO(?+qz1;z;DS>r=v44zGbp6@gL<7VcZU!AQ&J9vi{ zc{G>QNPn~H7L6>=_r7i1``A&{qRuuY4crWTV4Be-cRt?eG8YW1&P5FP%Bqw5+nWpB zT)yl!IZ9WE=+`+h{H7$%n|M$I{5#{nfCdEu6>q4on2 z^<^7MzYoyn@IftGzxTWGK^}`4qy>7peC+(t`axbB=H@>bPAwWH^0q_g0Xm~X-fo2| zg{nhT@Q0eFlKHfo<;6I4o5Z?O*YVM^sDt&RMOFQQiw5lP+iN?R*m1sr&j;L_NBOC5r{FBUwI@#X$voiS@ zx7RP)Da&N|`f~emq%U#S9X@SqsC!liQ17hu+xVOo>7V&HxsqR1nm*P6@)?6Ux;Ix~ zqxL?zD;@p1k83SHiF^A4F_avOoPeB+oZ9!2jF&mpk=v3IIoA2LkvS5U49kO6z*=Dc z*ZEfek~aROBg@hFMSl~TnIS0Dcai0CedJlRJTfKpW(X(H18GJe5O^HK1>5J1yd8hu zo8*udI*c@C;HGasA~*0P=aLBB{Iu4Xa*vP_Snb$xTA6CNc#rcq z^huKG;kSkz<%SdbMXyZl{gzqp-*Wbp;ushDsFe3RUh%O5jZ(lF?YB)4N=n)nn3wb) zxS%OM2JS~5SvWc$!?65C3apdi_D}QSPkoP@I*!jl#{L~^%f&_X17dz168Cxxae8KL z&P)uLQn)C1Q54?f0qdWGw7I1$hK4Ch$g9X3$lJ)fmubV44Lb9=fCHd;u)YwZ*kC$t zx@8J7BR9MF^5nC=dWcLo?#Hs+mCo51#A=Ex6!Unn4Wvbu$k7?^p6uv7MC)`NTp8*e zhB+<8h3>@5DsibP>MBO_x7j*U#)11uTlj`biwBp1F9@i-3()rIDzDyd9!~giZ`YC! zx&u;y>?VaPO4Z(Eyt_zQSd-YK>rD~|EKX_(S>@Fz#H-bewKJXU68z%>tA0$?JC&v( z0g5GL80uO@(e9L{Hn6nT2xJ zi@+Sy9wK6Cas|OeSj^INp;7Fb76b*a3kX?KD&BN!)tZNE5tpl|#NWyPvox8s;g$#7 zCMUo2D>HZQ*Vd^1&&uQ#;Iwr^r9))UWZeuXU;yMTs;bl0t>OZe2rsRM5t-ZleaXus zRdN*n6c3jW*eW4fufF3_<$C9a_1n@g|Dqt^tH>tMO@xq;(7m$^gIi2RkiM8Q>5ywf zyqtox{tuKOLw^Uws5o&V8@d2lX zAAKNQkaj@!Y38$63IC8FAq+V5Apv*)qD&C*`H8J-BRe2%r)#(Qf2HWRUzBJ6%LV+C zr4#ti18r(Ow{&AM`vrjDqVr%nZv7MZ2Ymv-pQs&&@&m-nCi5r2l;mhXd;I{zUMd5? z>q=h|^l$3G=jp_x6?y`Ch%?^9;NHO4 z-@E}#f(VUpNofo%ZlLlL>Sx$h|;iRR0<*N?)o3&_2~;F_30lAxAwgEKkh z>;^(CEPpF6c?TU85Ec_?*_LW*5P)~&SXU>k{+;yC?U55rbA@ENe#8w@*)0t zp!K|~{HT0D#|Nv2Db=;w1V zyLrgM#j!Co!lYSjqC}lPR@dA<(0?6d)^!E0Kb@6D+4foh(`AgjA(4&N~ zeq~vM-0p?$s(0E#e_dbd)MMFp+ddCJ#)0~KA7uN($+cM6v5S*7|GuQ_+^ZuNBt{er zj# z07P{SS$YzIYfe?^{1h?g4Gi+&?HmRlUOc-OY`O5TpZq*O9 z59M9QgKQcebIc_=UoUA*bLzW+O&r!}Wfut#t}U*xFyD1CSP^#ZYLn@zYHUW<7VX~A zIKk zl$IDI?R6A+7QGb z&qaMMd^N{J%-Z2TU z{-dSyzNbmKuc^wdK)DiZF2}!(dRhX*6zhDWMnNr;bju}SP(<3^dWMcRQXEb}+{(9I zb?_EOS)qQ%2WODrsN+ePJ7C3stvfn5oN8+iJsI`QOZA8Y4nzFNda0atHmKwbu>_jZ zD#5@L#mm?C*VudiO!e%X+Q;a0r6{8ijjik1?*cWdo)Z1wlz32P+Y8|2{X3!2{5=?s z)SgFr!?#b*dw3mi8qiO>gl2Mf@VIgNk8;<)B7fx$oeOT38}VsTjKR4{*DlA_J&n3j zxUPu(|C4}9k%D_apV=IB38)*2osM+^g8X&e@e2$Rl9bT&v?ixbI(%j~eESZ>ZSMc4sxGqJM9>eQ0I=q|<9pVf5 zhSMk7eFV}MX*ZXQl*D-wT+mLmTdCvQ`B{0A^Q*8PYH5;G{MGiATta?i7Rn>GWfViD zvb@xu!dEL%UCPosZp`Q03YP2_TGGR@@_>q9mez4DLl9YVb3ZFzVt~2qOO4M@md4(} zUrO}fa4v5GiDagJM}D88X^*L-C9i~*?!?5|Iw>0IZ;4XkVjfHw7}U$J2{331R*M!? z#8>w`f1^ToKoD!r^_jVzYoM&5~H9@^p-vuUWnqs1IgQyO+T z^*m{T1gBk`6iB$HGsa7p#O#8!{SY2E!6^R?3Q&uwvpxIcFHELb<2-*;^*T&XX9$3KnYo$_e*h~2hvN|12eL*OA z`ieK=dLaT3ZUBD!((N?P#i&J2`8A0r1mDFzlX9@XMxxyCptw$_O{tc)0I#pDw(=i2 zM-B2D|D^I2#ysR19-fX`x}R|H57bE+(P%_Xl>`AVIqYrlZO%5Qi=l3aAL+EJ(MEdG zw%_FKx1U30pf9;V>9@rtFwL;TKnCX=KBO#y<-Y63-yGD{*L_0ACv*2I+WS3Co)t=> zvuw{0Ogeu$=6_eo8Iywc!I9UDBTj3#S8LfTUq9%&m}AUX$tuEGz`z?BmgV%P=*sVv z8W#qQvjB`5AY|NfHHm6YbG;|Lk<9CKDZ5p{AvmY1jalaUIpID&NEt+4*Wj4wu<7GC z_`AUw%6Wr6+B~7hzucg0b%A2}ktZhcZMo?c)Uwblg9!qtRm)dK-YvfvN^G{TP6V`T z=}rlru`3A|sm1N*q8KB(DW+L+aB2y4*ce<2P$he~{l&(rRCIUA-!ga4xi>mN@dfyS z0&-N;EuYZFtF-ypO|m1YqeOE~e!pwk62py8+h|SE#fj^sEEN{PWw-TH!=ecH{RsVY zzTVyI`aQF}Y6}0#15~BE(=v`PzvA zhDVb0bFl2Ks%<^GQWH?GPCbI7L`$XN^}<=8FEj&I_XTW)XlPzXjhhPGAYbMl`nBPr z-;+__|8^8!WcI|Y%q)F95MqEiJr}>@5=4)!Ci2l& zF(-N4oJM?WW?(x-b4?wLeP?e^F8YUOx8ac>yR8{rg-ee=G2CO(5=FJE@R#in@Y>fp zll0nL-{uv`kjCElrix=6Z*Ua9I&lTPRvoBZJd#wtXy%X9J_dVggeGmr;5W*f%Axd6 z=LVlrH+#M-0isaOg^kM-*eZ>lvk2Zjim)rn$0%vc0Dh@eQ*gmTDD~1t=iE}pRqEUl z+kVeq0ppe2@%KATiL&jwTRyBh_IBR`F<>YFA%HlKb9VmZE4;#Mm zv>B|xytP7-1W{(ZHB0U2D8Q}|tB4l_Ps$JRaJp|$QTLs_JNyJu{uleKO zB@4HDRa>_iU(-Ggk&EY>KEu3s>Ic@Cu~U}iCO>!HjnowISRlA*@!EV&Ui9+dNJ;NL z-v8N$0~Y#_=yQLr5BTM3M{gIBc;nJTnEzbVls0Y2{M34RaCkfYMDql?%3B8QJ1Sf# zvl)n#lT%C2u2!+w$bJG(X98w zD5;BW3MI@jc>+aZjPzOtG#eCoy?Y#c=i8T@ay9g6HBa7dWMBBbTL>@bi?m&*AVed# z%-7A^p#1x=IfL7|H=E*~+&wWHhny3p5tG9z8QOwnmhPG6qYI=yuQA;WL!K-4W8 zKN<=>)efHCjySmL9lC$2ZZ6cV^0Fb3e+17h#q&z$Dta7$*YFg7xIbT%Mz zICKittu1;26>O2NaQWVOe8{~(fL@)EsLD*+8uSF^@WydCIrgt<`scw^ z3g|1+Nvtme9q;J=uN(C_y6YBJkquv*f{#`+(TZ>6CwoLF+tHD{R$IT>uz1Pe{B|^g z3i?ZH`dR&qva}#7ICtik%??wE?py)W{h0Al3dp9-_O&qIo++ zwi=(Lu;_OwoeQ?nOz{b5etp$lRUreKy+!fvB#q^;K1oJmTiGQy$<}$nGK1 zWld>kOB~=Ki2DTY4(tz`%X>cWSkLU`fH`eX90l*POM%UeuW1v7GDu6h@$4i(Ljb%` z$S)t5E*F=Ka@&3rq-lzBD_Zb93Z>7lf!5LCP29`kCE^OfJ^n{yIQ*82zjnoQE57Sh zL%1ZY&`LBp(Jp>em=tWdGq)cjSZ0qElW$S(KGF)_U#w#crw1ug@oLL`FnHXYdvb1s zUgH&oF7BZKGBTdvnyujPG9!ntquknVXx>K|S|o2;R_-;Tn>8?+YX*Wa%XPxaf_R;GNyH1#;L&2 zofhIb&IDCl-|xv(E-X^P;a;P%OUee*l71+37H>1ZYW|lCU@e^6;BYCN>JnJ1y@zi? z%Xsm;JHMSA-C&41Q6ZHsbn-Anub-heHXjGU--b@2h?0`?6C8*JqqF%$H;HiD)a7)v48jm#toV^${C49$or)@3# z@FsNB2Z02+#Nd%ZxBuOrm9*@!@%woOI;U_jK4aatjg~KT*nmEe8e-D7{WkgM6zDa5lvoCr)d*0he?w1u_Vg)2N6;tg9*0`**kH&@8T6*E z>c?X43yRfE4*6qc8%^WIe3<5`p>f%kVI2r?J;bD(!{Nz*aMP}YI!J_lEPvj*q&{@4 zLCi(c;_5G?+j{@r`%1!b*59#3g>5Pa56osFd=R;sPU>-3?r|v0tBhD`h}NzuA0jik z*<@yA;_Y9#dUF@9WZ=xmPdeT#Vnijwmn4=jXyCG<2Cq38gVo*)Nt_LB_8@InK1bGNEd7)Q-DtDfU%a{ zafV|dG)2`25E&(3q{PRI>lL^@?)AkOJwilqmG66MKqvcvSo9&~ExGU2GPRHt=O?tQ zogYVQ?k?;JsZ=~qQ6j?*#B7jgKimqvw+b517=1lE$Neq7Z}%Np;4WR{auxB46xxdH z7ex5-Z=^7k!N%Ho-0^>=(IoScN+^QNyW+=1*+68|C5U8vgj1%VU^J0q_Wc?a_l=zQ zDShH7Z`v%{QK{DjHyGQtuj-w`E!r4<*Z2^iez}27-;9$(j@DB~g18QUW*`S^G--R& zX-Te7Cd#;3NK0gqkr8I_(GbmNXm0nzfJru-cEH ziZQvQBc>GG?HZ7Dvnp`&b(Q&`^a6gAG4lR9dK)Ys!cfMOj~d_BNfooU7h1|n$K{%?i>~NX7oX+ILdYD0(QAdI!P8Wk6w5b@lPklAs+bmv=N)?7 z8`tv*$g=u+n!ei^qglz8QxK3chQC-W-*r+Q^<+VecPrtHnOiD!M_jDk+5E4%zA~W6 zsPB6lpoCJ=ARr+j-6<`Cgot#F?iK_Q7$`_cC?ZHp2+~p#(kYD~C2ZsX3F(GCXWaMu zetMp78;k$hxz4%HIlp4(uacg@m!(EGHEvS13M73I!H2!qZ}DCltR6i`T`Hh*dBOOI z!P*QcPd>|v<0@TGF5M?;7eoj0oYT^ehmPh;7Hald(d_%j%DIQvrL9c`v|RQ^+#A># zBAfr+r>VYKAFk+k$y450x9&wvy#53XSmGlW6t3eUQNm(^Dty{7Z>$+7i!f zX~u-KCe0y>1id)#2Xq$aJ}i$mGkRAdHTE-UmMmk#TgHA`!drGo_|?Gi(p=wmX6g=7 zIQdd`($rng^Bx~6^>D+urEG2b@mGH6F<+N$o#nNpWPrS<$ zdygN>UmLd3i)iBXuh!q-VnFz!^nm`6j=5VYbwNUEdfBoqI%Ixh)|=A{w7p}*ef|J5 z7b)$5(gO^^7YC}|d9GOPQq$1g82GULO(Q+&lfDPIyI3{wKdQ$ku>^+b+<4EABQ@D= z$|qso0y~dh94^Gt@@mLJkYwCmW97WH^7$gr zGx^ZPAzgy(zm1>q+TB{bKj3gas7rH@;(vE9Zn4~>=BLL6oq+c}7*)r@aY(1_AL(U& z*c8w@^xQRDPQMQOC@BYbutV4-qm5wH;Txff9yd8$rtIw*`2RS#%@&^wLK)nw!W*vxMv@$KuAxkIPswXxG}5$lLP3GQbya+f)`j!IEw9AR+{@ zA*@e;eKMy8W2Ay;?>@=9!ojhac!;CABnuXU#wLcO?YyrBY#A|rLBtFIi1hh|DNOVFXr%u!I#>eE*?m%nh5q3yJ}S2_kJl z-}0&$5IjKsRfRcSwO^8b?hdr)D~kInU*>-Y-skSX3e|(a_wTlwf}<~@zQBm%EM%sA(H!InJR+A)QNL|8lPsanw}oZ z4Wv%<2O46Sn-B^pvMpI!R#86J(*ke%P7{3$d*3CoK#?XzS~9Ij7hiPy&ikU!HZx+S zmujp)Z`s^H6O-y@Ibuhj#(s0MO)0ZYF-z+7->`FZ--i&KxOQ5DY@Yzf7Al^pb2cz2 zXZ?_RBN$VL;EXkNhk<_cBb-8=Ri%gcV-c4=vR|%w2+i#){6f^*RP33-;%}G!uG_V# z(a}9e!x#eiq8=3*Oq@P0fz}gdQ&9&Jus|8fcHU*C1Y)FOx4d&du{r-C_z)$Px-Xgo zh97OG5r^>B`hf9bhumUvWx@lD-}~BY`%)5@n*UfxHDEIITUF__N7`f;F)LK34B_5o zs!5x<&~Vl9OW?pz>RUxs+|;6vu*i&R%iiyNj1z>h_XtY9uJ&SG-moXqgDfJufJQYl zQe-4p_`~d>FdYyf3|x1)`~Cz&j_4N_l@vPlW?6q*9T0Pm22u;#jCntIMBq5oXzp=BYSvfzl^$SfZV%vHruBF| z-2=+IY7AVL3l1P!{JSBX51L~DqxdC;_&n#@`Hnl<7a9{Qm1WoW6b2_sFk_89f41c) z#S<8ZcqPHu`5&Vk-?ad>;YYZk>Me>>4}KioKnAsay7p|AY-KjWyx#kMyL31%v$s6$ zMO|t}fb8LUVx>m&MTatmG8+C>GVNVLH3r|1N(>9>Y!B*kqL#8QZ`aRnIDb1TgQl~v z>hi0k-FUauLy}*3>Gx{(9z$Vt`aWd_Jxm4T#C%HX2m4L8-MWXC%sRYq9sS%j$G!4l zGyj9F6_~6YrfDU%`zvg}rzSpkmkyF^VUWt0&b_RrW?_$tLrHMJ;dm{%CVHxAZ{S}u zO{bS+pq#}Oa_t`;IpP|t%|6PL-{XChu3oHAsx00RGNw-M4!P%tK}^@KJTBy3=t0rW zLgVpYS&0|G=PF#SO!6ngrT*%IMa?K@w|CVsrzR#Dw^Uf9w~xR*;K*9-dR%A!C1J z=p=8gd@nsOQM8pPR2asvA4kD_SxKptg8?FGH=3?M!agtkf?^eBX59A;3OAF*+i%1S zrlrkrj!@-5gGJgEG>sqPkVDn0wd{cbZEUt{)#53`*?c`PseRcRA4Abzs1nC$`sUS) z>VuRn=)KU)eA=7IqMfub#zfWhkkK;vXt7@q)m5Ol^AtR#76-P=h1|a3;=D~8!FJJm z{cD@aM^+a5*qiukh$y^^O~xllCzS1OqyaTWGLe#YGkM8@_xnf!cGG*%hY@*5^#{RP zBAx|1->E}&wGV3YjdkjY`Q>ODCM8pPd2s(MW#i{^w0z5=y|uFs@6Sv`)_%e3w8mKX zyIPGQ)CaDQDJSe#;xcok@Y2&<(2JI)I4`KRe7q@k-<7+QKF)G%9o*4H_DLF*nPs6) zEpez%320lQQTmB*lPeE}sj7TbW$)J$C%?8}hY)-lGo&q1UbH*j%=Fk`8a*$zj#1WQ z5_j$RqrK4hj`I($=DEJYh#XnD0%G=LCo|Pq$$+og(rmN?COWs_T&^h&TC?K+JKQq8an8W_}Z?W4KCItT$a9CaBx{L>KkAwqRvGOpit^j^|-YGvL; zD|%alx#+vlhl6iexBNE6dhsnWtd*UEw(#HA2%n*X4tAD0@;?JkvA_VVr&BT-vTM1W-yN>LdVprLlF zJMOs{R|r3<>mH^p+$ZYt$^s6sm3};m98||TMo^YJpVsFOSsE#NenZ4kVuKx`HvMT) zEN^{#w1X`*p^R~l(VQXVH*jz!2>+rY^H4-wxAvDr2HzLUsTomIha)w3EOK87(uxqs z3kgBGcThpUrWzaGUb4Qi>a;S1ncx)PkCMkBFr?}gOR;$O@{GWxq6Av`1->NvZBfSn z&fx~>{MupKZ@lG=R;X6mbZV*99>@twRvtmkjb`H8e1ODt$K;{e<{H6nj>i@G_ELM6 zHej_rJNStq&|o_BUkmrT_RRP1RJAxN!K!Cc_5Wc37|pXm)3O?uJIQQyi8WyNP|E=~ zLRZv_Gj@0L=hY1rNu8=vEIip6gr-03JcHBYyfD6*EJ-_bqidmV7>RyGrAK+8fuMU_ zPgN!xI^xdW*rYpx@+Eo%amlJ+dH5MR^CzRJPo^QtPE05v5^Ty|;cv^ERKhTg)Tkes z%uI}NJMWkdy2yvWHmHLHzK09{W>(0ZfJZUnpR35jv?tCg%pJHDOHw4?6CXVecF`X} z-SHqmxWZ>v7U;GQ0`yig07eAtj+GjJohQF=Dv|Zv2sz%N!Gz`c#a7-mIOuvN(-ku5 zmjrSIp1P8wRvsWbc$|5C|8=f-@#^8`^vR4ihU?AKNDfpRCf zDDPRBQx0Wo1)kpl%5N0or0}(Peuuay5CF<U1yO~J$gWZ7KmL2W7o!rm0xX9pdpCtCdj*sC5IbS1A5;C$~OYx7E9;H3bW+0n! z^+C*gz27DnFTj+=OOFR}N4{%ZVO*^Xn3YNr>Cy`HoR&8ggyrwQq2<^)??Bjw;IMk^ zSCDccx-GE&`T3*Zw#1`QZ+Z~lm4Q34$H!iXw{Y2QUI^| zWzgc6$i8Y8pla++i$Pgfp$TXr$^ZCU^E#V6-6-ak!SOhy_ZTGXdF^BQS&~wEUh&yR zhz())0Yi0;fZvZ8p;jgTTA(2Jfby=YNt~kUIDEHKkwe60-`|queIHD~8-lP9H|%LB zLbrG8ZsNSqA&@%I7>}aKihhKO#@N z{??H~^3}XHgwv;MTW2EQ*UUW7>>rc;%~FzgqYaya*(9dDDcBITDV4e&{3O-Wc}?o= z8HKIw`RZEOqNLUvMB#&&A1?_ZPoK`64SjPmdT|`U5aIfqxMS9JEif^R6`WC2Yun&1 zpH@*#-Y*P-Gu$#>b(A{17=3B-7$;4?RfRlXMPxW*X=R|}27R;fbj8h6hHL)<#^jmR zx6pS2_F>NNRIJ|EcoGZNFzMX3ffGd_A*~$yP^=>Tqm3$r@z|Jac@CJ!2Z=# zuOqMRe-lD|=u~qG>Q9P{=#<>dC9Z>EGg4wV&A`vF&&d-ST}VaILcRN7!cJ3gVv%LG zC~Mf1q%gJZmnY$GUjq4p6zRIkL-$K7MpPVZ1ycNCHrQ=4eJD>`{PHgzZ`fi=mCrV(2-2K7LWF!Afp+x z;Wa+JrsVCHw!fcvcp8|YB30~rz@EXHl<=kQ79drA zLk(B|3O#=Q^)E$wUm3%ArT}44Qa*oz)5sD2>X-8U+)#Hg7lY{YcjIBh-p}`ESQ9l5 z#F#*SUH#xWT(NF%{v|dvtAAm)TA+9+)0T0_J|70+SKZEDC18Bt+TJn+S7olo^~F#& zr{n93IF(!U;M#o8)ec@1BjMk==1rq5yP+Fe_^pk8+-ioSCgY~6|9XJLGhIVi?7T5M z6yG3VVz)*pN=vqd0AcK$@Y1LW|I+W&Wb@QJx1j)+?{v4bl#j8*%&)ZBh)jdgdVR{H zcq%rqT**BhH(oiJ8u(~TnzKmH7+t$UHwBU}8{IQ5^}BET#+aFYw-ISSY0*i~_C8O{ zC^db{W$i-wrf zSnp423kemuOJVPv@bu}wUj(E-z+7(;R6Y0N!-Mw8&=m(JQBawPovg08n{YRtB={Hq zK@S~bDvyRbn;KPlBnd^F3DM{9A}QWK$xH+SD^fN6wiGhh{;K|ZGcH@FYMKtLEN`jB z`UU5SJai)ND)zq{|)ro8#F-mN(Dx1R9om22;Ta9qE$9GZ6(V z@4LTRkzQg2#7!L|1;ypI8w#w??IB?*aDjux%_nI`m-i2A^`%`$nu^>AE8DpW#DICH z#>K0d=3-#L;sRdjEMv_AGBXFf!dq0-CpO%`N*ruZTU^d<+)Oj>yB zUj$6bVGe9ie0a_m&qx>?gXZ@=PmsId)l@K_bWz#(%iz>3A>^jZ?3};XB2HwRXE#>b z-&HwpXf{jn0N>J#4Pxo+{{~D1lZda@^WAI1e(e=+`q(e0Mx@PrlZJJ;jD)^yTu2Z% zPX>rFTyFql<0D<$-jq64uJfrRD<`$taDS=nn++q)wJI}-; z_tLv6%Dq6cSh*3|EQQ^zr*r*u%#aXkGQu|uv**jBU=BDvBzhZOJwAE4U@?^wotS9% zH)vBmNTV4$0p5l>f(c;PaQCox1Uu!d@r60+NV59x3_YR%_cQSi?vz;t5<|b;AMSi; zvqowv0j})#nmfK>IbtlotU^zl({Zk_docrIF72~zXv9Q+f z{NpHqn&_Ggh36!d)I7*+e`~&58KZKy^*1`G|mA(>&ne%Edg1c{Fx>G%Wa_%y; zerK6u<49|!k)P#yvzJ|w7|N${F|IK`$pbG_8fm)cm*OyLZg+`|1q-I1-bY+N^bJVq z4xX-BS#1rK{BqrO6q#ywBNR`?tx#XoSNW!A!t^ai&Y(Uj#OSe@bNgzuF4NP~DcjVH zUKgM=ZLuS_JRWlB$%1!}?h3s;A`mOp=7IQnZAZl{Bk>k?qp021b(#p!VzJdVzf5Y1 zZ7A5lKxVGCQ@5Zw4L+vIY_wW8z@hH%s`}T$##OoB`oMQ7n!fHU2M1(^9bD1zU*&lA z+MPqAzo^UIL_x5}PG(5QFOxnNW-y|mjy?NshE(IFZA7dBz;PH%;hmqK!kD(tuFvC( zdah-WYHBp>JV7?Fiy2Wknkemf&HsL(w78{B(r?A_20ftk9k&?a?{mkefklnacPHx( z{yRCDnm+ZNQRN@!h<70zZr!g(2r_Or2gMj!=rH06y}jI$+SOQH?~^a0k5f8Dal8G0L2< zyv)V_Vt!X>IHGS|b-p?&tU}Z8$WdTbn;|BNsYCXnH9?N}$a@AAg^0f3?h`V^Woh+N zMM|*2jpkhJZQ0R7A~>xiW^the_*8e20)*p67C1+XGBW2Vi9(sl}3H+*SUy%CTuuzc_#n3Ar zvUtf8mfiE5mj$AXtAGnaFOm_}%g(df_s_K~y=9Zur9P`l8VPK!kMw>u_6Huk9wJ}L z6ndD>tzA=9*(g=F!mm%1nYF=i@Q0OIc`E_Iv}xRu2%D?h4_z*nVF4I1Zk8n;S2h09 zD|w}^2D415rFNY}E*Kx!*_z7hl4p`ez`{B`N}ypXTcfA1;|poMVDIA3_>XY3va(t< zaB98Z!{BR>$jZ?G0RjgMv0X!N5jV^Z)>3%7v7bo>`R?-vyHr;%}T zD=57|NW_1r{9DLO*24u}(DFR15@0frwyI--g)Uon>-hkXK^_Ljd9QJ<&KwnP+G|AK z@cJnSB*-=Jx3nzy#ESSZvhdK`U(jczrj@rPHP#E>!E~%^UoqFASp5k z^HvZBgYSFRD}AMLR|j`7iWo6@e1K)a0N+Epica*#g8K1S0b=y7Sl%H-{?@}gkdQ-@ z!K?W9|2;1M3F+{c|I4_j%*+5T@VtY)y*a?_l$j~Ov(FB8l3ZAJ;sD?ve}zYYy$D!j z@j`H6q=e1OxmX_Hbp`-9_8Slo>|>Db!d%SD->kw6g_aOfgFddEHbF)q96Wr^ArMFp z_WuZghT@h_$2k+9i!*I}vgd>d0}`lydS2h^3EN5Z2) Date: Sat, 31 Jan 2026 12:08:45 +0100 Subject: [PATCH 12/65] chore: added `WebUntisService` to refresh account --- services/shared/types.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/shared/types.ts b/services/shared/types.ts index 5fca4868..fcdbe6a5 100644 --- a/services/shared/types.ts +++ b/services/shared/types.ts @@ -35,6 +35,7 @@ import { Izly } from "../izly"; import { Multi } from "../multi"; import { Skolengo } from "../skolengo"; import { TurboSelf } from "../turboself"; +import { WebUntisService } from "../webuntis"; import { Balance } from "./balance"; import { Kid } from "./kid"; @@ -64,7 +65,7 @@ export interface SchoolServicePlugin { refreshAccount: ( credentials: Auth - ) => Promise; + ) => Promise; getKids?: () => Kid[]; getCanteenKind?: () => CanteenKind; getHomeworks?: (weekNumber: number) => Promise; From acdb8fe0f68ea94e60480f556b6accd6423c6e0a Mon Sep 17 00:00:00 2001 From: LeChacal Date: Sat, 31 Jan 2026 12:14:33 +0100 Subject: [PATCH 13/65] chore: created credentials.tsx file --- app/(onboarding)/webuntis/credentials.tsx | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 app/(onboarding)/webuntis/credentials.tsx diff --git a/app/(onboarding)/webuntis/credentials.tsx b/app/(onboarding)/webuntis/credentials.tsx new file mode 100644 index 00000000..e69de29b From e3ec514381f757b84faf39acf735afc81fcf6659 Mon Sep 17 00:00:00 2001 From: LeChacal Date: Sat, 31 Jan 2026 12:25:10 +0100 Subject: [PATCH 14/65] feat: same credentials screen as ED --- app/(onboarding)/webuntis/credentials.tsx | 321 ++++++++++++++++++++++ 1 file changed, 321 insertions(+) diff --git a/app/(onboarding)/webuntis/credentials.tsx b/app/(onboarding)/webuntis/credentials.tsx index e69de29b..d4ed2c91 100644 --- a/app/(onboarding)/webuntis/credentials.tsx +++ b/app/(onboarding)/webuntis/credentials.tsx @@ -0,0 +1,321 @@ + +import { Client, DoubleAuthQuestions, DoubleAuthResult, Require2FA } from "@blockshub/blocksdirecte"; +import { useTheme } from "@react-navigation/native"; +import { router } from "expo-router"; +import React, { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { + Keyboard, + KeyboardAvoidingView, + Modal, + Platform, + Pressable, + View, +} from "react-native"; +import Reanimated, { + FadeInDown, + FadeOutUp, + useSharedValue, + withTiming, +} from "react-native-reanimated"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; + +import OnboardingBackButton from "@/components/onboarding/OnboardingBackButton"; +import OnboardingInput from "@/components/onboarding/OnboardingInput"; +import OnboardingScrollingFlatList from "@/components/onboarding/OnboardingScrollingFlatList"; +import { useAccountStore } from "@/stores/account"; +import { Account, Services } from "@/stores/account/types"; +import { useAlert } from "@/ui/components/AlertProvider"; +import AnimatedPressable from "@/ui/components/AnimatedPressable"; +import Button from "@/ui/components/Button"; +import Stack from "@/ui/components/Stack"; +import Typography from "@/ui/components/Typography"; +import uuid from "@/utils/uuid/uuid"; + +const ANIMATION_DURATION = 170; +export const PlatformPressable = Platform.OS === 'android' ? Pressable : AnimatedPressable; + +export default function WebUntisLoginWithCredentials() { + const insets = useSafeAreaInsets(); + const theme = useTheme(); + const { colors } = theme; + + const alert = useAlert(); + const { t } = useTranslation(); + + const [challengeModalVisible, setChallengeModalVisible] = useState(false); + const [doubleAuthChallenge, setDoubleAuthChallenge] = useState(null); + + const [session, setSession] = useState(null); + const [token, setToken] = useState(); + + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + + const [isLoggingIn, setIsLoggingIn] = useState(false); + + const opacity = useSharedValue(1); + const scale = useSharedValue(1); + + const keyboardListeners = useMemo(() => ({ + show: () => { + "worklet"; + opacity.value = withTiming(0, { duration: ANIMATION_DURATION }); + scale.value = withTiming(0.8, { duration: ANIMATION_DURATION }); + }, + hide: () => { + "worklet"; + opacity.value = withTiming(1, { duration: ANIMATION_DURATION }); + scale.value = withTiming(1, { duration: ANIMATION_DURATION }); + }, + }), [opacity]); + + useEffect(() => { + const showSub = Keyboard.addListener("keyboardWillShow", keyboardListeners.show); + const hideSub = Keyboard.addListener("keyboardWillHide", keyboardListeners.hide); + + return () => { + showSub.remove(); + hideSub.remove(); + }; + }, [keyboardListeners]); + + const handleLogin = async (username: string, password: string, keys?: DoubleAuthResult) => { + const client = new Client(); + const device = uuid(); + const store = useAccountStore.getState(); + + try { + const tokens = await client.auth.loginUsername(username, password, keys?.cn, keys?.cv, true, device); + if (tokens) { + client.auth.setAccount(0); + const authentication = client.auth.getAccount(); + const account: Account = { + id: device, + firstName: authentication.prenom, + lastName: authentication.nom, + schoolName: authentication.nomEtablissement, + services: [ + { + id: device, + auth: { + additionals: { + "username": username, + "token": authentication.accessToken, + "cn": keys?.cn ?? "", + "cv": keys?.cv ?? "", + "deviceUUID": device + } + }, + serviceId: Services.ECOLEDIRECTE, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + }, + ], + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + + store.addAccount(account); + store.setLastUsedAccount(device); + + queueMicrotask(() => { + router.push({ + pathname: "../end/color", + params: { accountId: device }, + }); + }); + } + } catch (e) { + setIsLoggingIn(false); + if (e instanceof Require2FA) { + const questions = await client.auth.get2FAQuestion(e.token); + setDoubleAuthChallenge(questions); + setSession(client); + setChallengeModalVisible(true); + setToken(e.token); + } + alert.showAlert({ + title: "Erreur d'authentification", + description: "Une erreur est survenue lors de la connexion, elle a donc été abandonnée.", + icon: "TriangleAlert", + color: "#D60046", + technical: String(e), + withoutNavbar: true, + }); + } + } + + const loginED = async () => { + if (!username.trim() || !password.trim()) { return; } + setIsLoggingIn(true); + Keyboard.dismiss(); + await handleLogin(username, password); + setIsLoggingIn(false); + }; + + async function handleChallenge(index: number) { + setChallengeModalVisible(false); + + if (!session || !doubleAuthChallenge?.propositions?.[index]) { return } + try { + const keys = await session.auth.send2FAQuestion(doubleAuthChallenge.propositions[index], token ?? ""); + queueMicrotask(() => void handleLogin(username, password, keys)); + } catch { + throw new Error("2FA challenge failed"); + } + } + + function questionComponent({ item, index }: { item: unknown; index: number }) { + return ( + + { + handleChallenge(index); + }} + style={{ + paddingHorizontal: 10, + paddingVertical: 10, + paddingRight: 18, + borderColor: colors.border, + borderWidth: 1.5, + borderRadius: 80, + flexDirection: "row", + alignItems: "center", + gap: 16, + }} + > + + + {index + 1} + + + + + {String(item)} + + + + + ); + } + + return ( + + + + + + + {t("STEP")} 2 + + + {t("STEP_OUTOF")} 3 + + + + {t("ONBOARDING_LOGIN_CREDENTIALS")} Ecole Directe + + + + + + + { + Keyboard.dismiss(); + if (!isLoggingIn && username.trim() && password.trim()) { loginED(); } + }, + returnKeyType: "done", + editable: !isLoggingIn, + }} + /> +