From f9266ee6952697be8c27dd6946c318651b2d952e Mon Sep 17 00:00:00 2001 From: KoenDR06 Date: Mon, 1 Sep 2025 23:26:30 +0200 Subject: [PATCH] auth works --- src/main/kotlin/Main.kt | 25 +++++++++++---- src/main/kotlin/authenticate.kt | 51 ++++++++++++++++++++++++++++++ src/main/kotlin/getEtage.kt | 4 +-- src/main/kotlin/getFloorInfo.kt | 6 ++-- src/main/kotlin/getOffers.kt | 1 + src/main/kotlin/requestTemplate.kt | 8 ++--- 6 files changed, 79 insertions(+), 16 deletions(-) create mode 100644 src/main/kotlin/authenticate.kt diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 71fae6e..bc530f3 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -1,6 +1,7 @@ package me.koendev import io.github.cdimascio.dotenv.Dotenv +import me.koendev.utils.println import java.io.File import java.time.LocalDate import java.time.format.DateTimeFormatter @@ -15,23 +16,30 @@ data class ReactableOffer( ) fun main() { + val sessionToken = auth() + + print("Getting rooms") val rooms = getRooms().filter { room -> room.unitType == config.general.unitType } if (rooms.isEmpty()) { - println("No suitable offers were found, quitting.") + System.err.println("No suitable offers were found, quitting.") return } + val offers = getOffers(rooms.map { it.wocasId }).offers.filter { offer -> offer.adres[0].plaats in listOf( "UTRECHT", ) + if (config.general.allowZeist) "ZEIST" else "" } + print("\rFiltering on personal filters") + var index = 0 val coupled = rooms.mapNotNull { room -> val offer: Offer? = offers.find { room.wocasId.toInt() == it.eenheidNummer.toInt() } - if (offer == null) null else ReactableOffer(room, offer, getFloorInfo(room)) + print("\rFiltering per room: ${index++} / ${rooms.size}") + if (offer == null) null else ReactableOffer(room, offer, getFloorInfo(room, sessionToken)) }.filter { val gender = it.floor.floorInfo.genderPreference @@ -40,13 +48,16 @@ fun main() { val date2 = LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd")) val daysLeft = ChronoUnit.DAYS.between(date1, date2) + val smoking = it.floor.floorInfo.smokingAllowed ?: true + val pets = it.floor.floorInfo.petsAllowed ?: false + ((gender == "female" && config.gender.female) || (gender == "male" && config.gender.male) || (gender == "none" && config.gender.none)) && - ((config.general.smoking == -1 && !(it.floor.floorInfo.smokingAllowed ?: true)) || (config.general.smoking == 1 && it.floor.floorInfo.smokingAllowed ?: true) || config.general.smoking == 0) && + ((config.general.smoking == -1 && !smoking) || (config.general.smoking == 1 && smoking) || config.general.smoking == 0) && - ((config.general.pets == -1 && !it.floor.floorInfo.petsAllowed) || (config.general.pets == 1 && it.floor.floorInfo.petsAllowed) || config.general.pets == 0) && + ((config.general.pets == -1 && !pets) || (config.general.pets == 1 && pets) || config.general.pets == 0) && daysLeft == 0L } @@ -76,7 +87,7 @@ fun main() { str.append("| Geslacht | ${genderString.padEnd(18, ' ')} |\n") str.append("| Roken | ${(if (it.floor.floorInfo.smokingAllowed ?: true) "✅ Mag" else "❌ Mag niet").padEnd(17, ' ')} |\n") - str.append("| Huisdieren | ${(if (it.floor.floorInfo.petsAllowed) "✅ Mogen" else "❌ Mogen niet").padEnd(17, ' ')} |\n") + str.append("| Huisdieren | ${(if (it.floor.floorInfo.petsAllowed ?: false) "✅ Mogen" else "❌ Mogen niet").padEnd(17, ' ')} |\n") val positionString = "${it.floor.potentialPosition} / ${it.floor.applicantCount}." str.append("| Reacties | ${positionString.padEnd(18, ' ')} |\n") @@ -90,11 +101,11 @@ fun main() { val etage = getEtage(it.offer.assSubjectPersk.first { it.pkHeeftAsp == 52.0 }.waarde!!) - str.append("![Foto](${etage.photos[0].etagePhoto[0].url})\n\n") + if (etage != null) str.append("![Foto](${etage.photos[0].etagePhoto[0].url})\n\n") str.append("### Message: \n\n${it.floor.floorInfo.description ?: "Deze pannekoeken hebben geen bericht achtergelaten"}\n") str.append("\n\n") } out.writeText(str.toString()) - println("${coupled.size} offers found, wrote to $fileName") + println("\r${coupled.size} offers found, wrote to $fileName") } diff --git a/src/main/kotlin/authenticate.kt b/src/main/kotlin/authenticate.kt new file mode 100644 index 0000000..8598c69 --- /dev/null +++ b/src/main/kotlin/authenticate.kt @@ -0,0 +1,51 @@ +package me.koendev + +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonIgnoreUnknownKeys +import me.koendev.utils.println +import java.net.URI +import java.net.http.HttpClient +import java.net.http.HttpRequest +import java.net.http.HttpResponse + +fun auth(): String { + val data: Map = mapOf( + "Email" to dotEnv["EMAIL"], + "Password" to dotEnv["PASSWORD"], + "Pin" to "", + "Role" to "" + ) + val req = HttpRequest.newBuilder() + .uri(URI.create("https://www.sshxl.nl/api/portal/ApiLogin")) + .header("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0") + .header("Accept", "*/*") + .header("Content-Type", "application/json") + .header("Accept-Language", "en-US,en;q=0.5") + .header( + "Cookie", + listOf( + "cookie_consent_analytics=no", + "cookie_consent=no", + "SSHContext=${dotEnv["AUTH"]}" + ).joinToString("; ") + ) + .POST(HttpRequest.BodyPublishers.ofString(Json.encodeToString(data))) + .build() + + val client = HttpClient.newBuilder() + .followRedirects(HttpClient.Redirect.NORMAL) + .build() + + val res = client.send(req, HttpResponse.BodyHandlers.ofString()) + + if (res.statusCode() != 200) { + throw Exception("auth failed with status code ${res.statusCode()}") + } + + return Json.decodeFromString(res.body()).session +} + +@JsonIgnoreUnknownKeys +@Serializable +private data class Auth(val session: String) \ No newline at end of file diff --git a/src/main/kotlin/getEtage.kt b/src/main/kotlin/getEtage.kt index 613a642..eadca11 100644 --- a/src/main/kotlin/getEtage.kt +++ b/src/main/kotlin/getEtage.kt @@ -33,8 +33,8 @@ data class EtagePhotoItem( @SerialName("Photo") val url: String ) -fun getEtage(id: String): Etage { +fun getEtage(id: String): Etage? { val response = getEndpoint("OData/Etage?\$filter=(EtageWocasId%20eq%20'$id')&\$expand=Etage_EtagePhoto!(\$select=Id,EtagePhotoId,EtageId;\$expand=EtagePhoto!(\$select=Id,Photo))&\$select=Id,EtageWocasId") - return Json.decodeFromString(response).offers.first() + return Json.decodeFromString(response).offers.firstOrNull() } diff --git a/src/main/kotlin/getFloorInfo.kt b/src/main/kotlin/getFloorInfo.kt index 2b49e4f..a4ee45a 100644 --- a/src/main/kotlin/getFloorInfo.kt +++ b/src/main/kotlin/getFloorInfo.kt @@ -10,7 +10,7 @@ data class FloorInfo( @SerialName("Description") val description: String?, @SerialName("HospiteerDate") val hospiteerDate: String?, @SerialName("PreferenceSmokingAllowed") val smokingAllowed: Boolean?, - @SerialName("PreferencePetsAllowed") val petsAllowed: Boolean, + @SerialName("PreferencePetsAllowed") val petsAllowed: Boolean?, @SerialName("PreferenceGender") val genderPreference: String, @SerialName("NumberOfUnits") val numberOfUnits: Int, ) @@ -41,8 +41,8 @@ data class Floor( @SerialName("Kind") val kind: String, ) -fun getFloorInfo(room: Room): Floor { - val response = getEndpoint("offer/${room.flowId}") +fun getFloorInfo(room: Room, session: String): Floor { + val response = getEndpoint("offer/${room.flowId}", session) return Json.decodeFromString(response) } diff --git a/src/main/kotlin/getOffers.kt b/src/main/kotlin/getOffers.kt index a6f4982..ddaff4a 100644 --- a/src/main/kotlin/getOffers.kt +++ b/src/main/kotlin/getOffers.kt @@ -3,6 +3,7 @@ package me.koendev import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json +import me.koendev.utils.println @Serializable data class Offers( diff --git a/src/main/kotlin/requestTemplate.kt b/src/main/kotlin/requestTemplate.kt index 5d82fde..cb0a2d4 100644 --- a/src/main/kotlin/requestTemplate.kt +++ b/src/main/kotlin/requestTemplate.kt @@ -10,7 +10,7 @@ import java.net.http.HttpResponse * * @return a HttpRequest with all the necessary properties to GET the endpoint. */ -fun buildRequest(path: String): HttpRequest { +fun buildRequest(path: String, session: String): HttpRequest { return HttpRequest.newBuilder() .uri(URI("https://www.sshxl.nl/api/v1/$path")) .header("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0") @@ -21,19 +21,19 @@ fun buildRequest(path: String): HttpRequest { listOf( "cookie_consent_analytics=no", "cookie_consent=no", - "SSHContext=${dotEnv["AUTH"]}" + "SSHContext=$session" ).joinToString("; ") ) .GET() .build() } -fun getEndpoint(endpoint: String): String { +fun getEndpoint(endpoint: String, session: String = ""): String { val client = HttpClient.newBuilder() .followRedirects(HttpClient.Redirect.NORMAL) .build() - val request = buildRequest(endpoint) + val request = buildRequest(endpoint, session) return client.send(request, HttpResponse.BodyHandlers.ofString()).body() } \ No newline at end of file