initial commit

This commit is contained in:
KoenDR06 2025-05-31 16:32:15 +02:00
commit da99eb4f70
15 changed files with 724 additions and 0 deletions

68
src/main/kotlin/Main.kt Normal file
View file

@ -0,0 +1,68 @@
package me.koendev
import java.io.File
data class ReactableOffer(
val room: Room,
val offer: Offer,
val floorInfo: FloorInfo
)
fun main() {
val rooms = getRooms().filter { room ->
room.unitType == config.general.unitType
}
val offers = getOffers().offers.filter { offer ->
offer.adres[0].plaats !in listOf(
"ZWOLLE",
"GRONINGEN",
"TILBURG"
) + if (!config.general.allowZeist) "ZEIST" else ""
}
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))
}.filter {
val gender = it.floorInfo.genderPreference
((gender == "female" && config.gender.female) ||
(gender == "male" && config.gender.male) ||
(gender == "none" && config.gender.none)) &&
((config.general.smoking == -1 && !it.floorInfo.smokingAllowed) || (config.general.smoking == 1 && it.floorInfo.smokingAllowed) || config.general.smoking == 0) &&
((config.general.pets == -1 && !it.floorInfo.petsAllowed) || (config.general.pets == 1 && it.floorInfo.petsAllowed) || config.general.pets == 0)
}
val fileName = "offers.md"
val out = File(fileName)
val str = StringBuilder()
coupled.forEach {
val address = it.offer.adres[0]
str.append("## [${address.straatnaam} ${address.nummer}, ${address.plaats.lowercase().capitalize()}](https://sshxl.nl/nl/aanbod/${it.room.flowId}-${address.straatnaam.lowercase().replace(" ", "-")})\n")
str.append("- Huisgenoten: ${it.room.numberOfRooms-1}\n")
val genderString = when (it.floorInfo.genderPreference) {
"none" -> "Geen voorkeur"
"male" -> "Man"
"female" -> "Vrouw"
else -> it.floorInfo.genderPreference
}
str.append("- Geslacht: $genderString\n")
str.append("- Roken: ${if (it.floorInfo.smokingAllowed) "Mag" else "Mag niet"}\n")
str.append("- Huisdieren: ${if (it.floorInfo.petsAllowed) "Mogen" else "Mogen niet"}\n")
str.append("\n")
str.append("### Message: \n\n${it.floorInfo.description}\n")
str.append("\n\n")
}
out.writeText(str.toString())
println("${coupled.size} offers found, wrote to $fileName")
}

24
src/main/kotlin/TOML.kt Normal file
View file

@ -0,0 +1,24 @@
package me.koendev
import de.thelooter.toml.Toml
import java.io.File
data class GeneralConfig(
val unitType: String,
val allowZeist: Boolean,
val smoking: Int,
val pets: Int
)
data class GenderConfig(
val male: Boolean,
val female: Boolean,
val none: Boolean
)
data class Config(
val general: GeneralConfig,
val gender: GenderConfig,
)
val config: Config = Toml().read(File("config.toml")).to(Config::class.java)

View file

@ -0,0 +1,48 @@
package me.koendev
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@Serializable
data class FloorInfo(
@SerialName("WocasId") val wocasId: String,
@SerialName("Description") val description: String,
@SerialName("HospiteerDate") val hospiteerDate: String?,
@SerialName("PreferenceSmokingAllowed") val smokingAllowed: Boolean,
@SerialName("PreferencePetsAllowed") val petsAllowed: Boolean,
@SerialName("PreferenceGender") val genderPreference: String,
@SerialName("NumberOfUnits") val numberOfUnits: Int,
)
@Serializable
data class RoomInfo(
@SerialName("ExpireBy") val expireBy: String,
@SerialName("RemainingTime") val remainingTime: String,
@SerialName("ApplicantCount") val applicantCount: Int,
@SerialName("CurrentPosition") val currentPosition: Int,
@SerialName("CurrentAdjustedPosition") val currentAdjustedPosition: Int,
@SerialName("PotentialPosition") val potentialPosition: Int,
@SerialName("ViewingDate") val viewingDate: String,
@SerialName("FloorInformation") val floorInfo: FloorInfo,
@SerialName("SubsidiabeleHuur") val subsidiabeleHuur: Double,
@SerialName("BruttoHuur") val brutoHuur: Double,
@SerialName("IsPublished") val isPublished: Boolean,
@SerialName("PublishedOn") val publishedOn: String?,
@SerialName("ContractStartDate") val contractStartDate: String,
@SerialName("UnitType") val unitType: String,
@SerialName("NumberOfRooms") val numberOfRooms: Int,
@SerialName("Image") val image: String?,
@SerialName("ContractType") val contractType: String,
@SerialName("FlowId") val flowId: Int,
@SerialName("WocasId") val wocasId: String,
@SerialName("Kind") val kind: String,
)
fun getFloorInfo(room: Room): FloorInfo {
val response = getEndpoint("offer/${room.flowId}")
return Json.decodeFromString<RoomInfo>(response).floorInfo
}

View file

@ -0,0 +1,87 @@
package me.koendev
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import java.net.http.HttpClient
import java.net.http.HttpResponse
@Serializable
data class Offers(
@SerialName("value") val offers: List<Offer>,
@SerialName("@odata.count") val count: Int,
@SerialName("isComplete") val isComplete: Boolean
)
@Serializable
data class Offer(
@SerialName("adrheeftenh") val adrHeeftEnh: Double,
@SerialName("eenheidnummer") val eenheidNummer: Double,
@SerialName("object") val `object`: Double,
@SerialName("soort_product") val soortProduct: String,
@SerialName("ASSSUBJECTPERSK_H") val assSubjectPersk: List<AssSubjectPersk>,
@SerialName("ASP_TOTAAL_C") val aspTotaal: List<AspTotaal>,
@SerialName("HUUROVEREENKOMST_H") val huurOvereenkomst: List<HuurOvereenkomst>,
@SerialName("EENHEID_HUUR") val eenheidHuur: List<EenheidHuur>,
@SerialName("ADRES_H") val adres: List<Adres>
)
@Serializable
data class AssSubjectPersk(
@SerialName("object") val `object`: Double,
@SerialName("pkheeftasp") val pkHeeftAsp: Double,
@SerialName("enhheeftkenm") val enhHeeftKenm: Double,
@SerialName("kenwaarde") val kenwaarde: String?,
@SerialName("waarde") val waarde: String?,
@SerialName("KENMERK_H") val kenmerk: List<Kenmerk>
)
@Serializable
data class Kenmerk(
@SerialName("object") val `object`: Double,
@SerialName("code") val code: String
)
@Serializable
data class AspTotaal(
@SerialName("eenheidnummer") val eenheidNummer: Double,
@SerialName("totopp") val totopp: Double,
@SerialName("totoppgem") val totoppgem: Double
)
@Serializable
data class HuurOvereenkomst(
@SerialName("object") val `object`: Double,
@SerialName("dcnnummer") val dcnnummer: Double,
@SerialName("einddatum") val einddatum: String,
@SerialName("enhheefthuu") val enhheefthuu: Double
)
@Serializable
data class EenheidHuur(
@SerialName("enh_object") val enhObject: Double,
@SerialName("eenheidnummer") val eenheidnummer: Double,
@SerialName("enh_nettohuur") val nettoHuur: Double,
@SerialName("bruto_huur") val brutoHuur: Double,
@SerialName("contract_type") val contractType: String
)
@Serializable
data class Adres(
@SerialName("oid") val oid: Double,
@SerialName("straatnaam") val straatnaam: String,
@SerialName("plaats") val plaats: String,
@SerialName("nummer") val nummer: String,
@SerialName("letter") val letter: String?,
@SerialName("toevoeging") val toevoeging: String?,
@SerialName("postcode") val postcode: String,
@SerialName("aanduiding") val aanduiding: String?,
@SerialName("locatie") val locatie: String?
)
fun getOffers(): Offers {
val response = getEndpoint("OData-mv?EENHEID_H?\$filter=((((((((((((((((((((((eenheidnummer%20eq%2033700034)%20or%20(eenheidnummer%20eq%2020214020))%20or%20(eenheidnummer%20eq%2026912101))%20or%20(eenheidnummer%20eq%2026912038))%20or%20(eenheidnummer%20eq%2082200297))%20or%20(eenheidnummer%20eq%2021001262))%20or%20(eenheidnummer%20eq%2036900015))%20or%20(eenheidnummer%20eq%2052000240))%20or%20(eenheidnummer%20eq%2051800177))%20or%20(eenheidnummer%20eq%2036400433))%20or%20(eenheidnummer%20eq%2051900083))%20or%20(eenheidnummer%20eq%2036400019))%20or%20(eenheidnummer%20eq%2046200340))%20or%20(eenheidnummer%20eq%2029602069))%20or%20(eenheidnummer%20eq%2033700216))%20or%20(eenheidnummer%20eq%2011010008))%20or%20(eenheidnummer%20eq%2051900057))%20or%20(eenheidnummer%20eq%2021002009))%20or%20(eenheidnummer%20eq%2052000218))%20or%20(eenheidnummer%20eq%2036400091))%20or%20(eenheidnummer%20eq%2046200669))%20or%20(eenheidnummer%20eq%2029601023))&\$expand=ADRES_H!(\$select=plaats,postcode,straatnaam,nummer,toevoeging,aanduiding,locatie,letter,oid),EENHEID_HUUR!(\$select=eenheidnummer,bruto_huur,enh_nettohuur,enh_object,contract_type),HUUROVEREENKOMST_H!(\$select=dcnnummer,einddatum,enhheefthuu),ASP_TOTAAL_C!(\$select=eenheidnummer,totopp,totoppgem),ASSSUBJECTPERSK_H!(\$select=enhheeftkenm,pkheeftasp,waarde,kenwaarde;\$expand=KENMERK_H!(\$select=code))&\$select=eenheidnummer,adrheeftenh,object,soort_product")
return Json.decodeFromString<Offers>(response)
}

View file

@ -0,0 +1,43 @@
package me.koendev
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import java.net.http.HttpClient
import java.net.http.HttpResponse
@Serializable
data class Image(
@SerialName("FilePathNL") val filePathNL: String?,
@SerialName("FilePathEN") val filePathEN: String?
)
@Serializable
data class Room(
@SerialName("ExpireBy") val expireBy: String,
@SerialName("RemainingTime") val remainingTime: String,
@SerialName("ApplicantCount") val applicantCount: Int,
@SerialName("CurrentPosition") val currentPosition: Int,
@SerialName("CurrentAdjustedPosition") val currentAdjustedPosition: Int,
@SerialName("PotentialPosition") val potentialPosition: Int,
@SerialName("ViewingDate") val viewingDate: String,
@SerialName("FloorInformation") val floorInformation: String?,
@SerialName("SubsidiabeleHuur") val subsidiabeleHuur: Double,
@SerialName("BruttoHuur") val brutoHuur: Double,
@SerialName("IsPublished") val isPublished: Boolean,
@SerialName("PublishedOn") val publishedOn: String,
@SerialName("ContractStartDate") val contractStartDate: String,
@SerialName("UnitType") val unitType: String,
@SerialName("NumberOfRooms") val numberOfRooms: Int,
@SerialName("Image") val image: Image,
@SerialName("ContractType") val contractType: String,
@SerialName("FlowId") val flowId: Int,
@SerialName("WocasId") val wocasId: String,
@SerialName("Kind") val kind: String,
)
fun getRooms(): List<Room> {
val response = getEndpoint("offer")
return Json.decodeFromString<List<Room>>(response)
}

View file

@ -0,0 +1,38 @@
package me.koendev
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
/**
* @param path the endpoint to GET. `sshxl.nl/api/v1/<PATH>`
*
* @return a HttpRequest with all the necessary properties to GET the endpoint.
*/
fun buildRequest(path: 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")
.header("Accept", "*/*")
.header("Accept-Language", "en-US,en;q=0.5")
.header(
"Cookie",
listOf(
"cookie_consent_analytics=no",
"cookie_consent=no",
).joinToString("; ")
)
.GET()
.build()
}
fun getEndpoint(endpoint: String): String {
val client = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.NORMAL)
.build()
val request = buildRequest(endpoint)
return client.send(request, HttpResponse.BodyHandlers.ofString()).body()
}