1 package net.pterodactylus.rhynodge.webpages.weather.wettercom
3 import net.pterodactylus.rhynodge.Filter
4 import net.pterodactylus.rhynodge.State
5 import net.pterodactylus.rhynodge.states.FailedState
6 import net.pterodactylus.rhynodge.states.HtmlState
7 import org.jsoup.nodes.Document
8 import org.jsoup.nodes.Element
9 import java.time.LocalDateTime
10 import java.time.format.DateTimeFormatter
13 * HTML parser for [wetter.com](https://www.wetter.com/).
15 * @author [David ‘Bombe’ Roden](mailto:bombe@pterodactylus.net)
17 class WetterComFilter : Filter {
19 private val dateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
21 override fun filter(state: State?): State {
22 if (state?.success()?.not() ?: true) {
23 return FailedState.from(state)
25 if (state !is HtmlState) {
26 throw IllegalArgumentException("")
29 return parseWetterComState(state)
32 private fun parseWetterComState(state: HtmlState): State {
33 val dateTime = parseDateTime(state.document()) ?: return FailedState(IllegalArgumentException("no date present"))
34 val wetterComState = WetterComState(dateTime)
35 parseHourStates(state.document()).forEach { wetterComState.addHour(it) }
39 private fun parseDateTime(document: Document): LocalDateTime? {
40 val dateElement = document.select("#furtherDetails .portable-mb h3")
41 .single()?.text()?.split(",")?.get(1)?.trim() ?: return null
42 val timeElement = document.select(".weather-strip--detail .delta.palm-hide")
43 .first()?.text()?.split(" ")?.first() ?: return null
44 return LocalDateTime.from(dateTimeFormatter.parse("%s %s".format(dateElement, timeElement)))
47 private fun parseHourStates(document: Document): List<WetterComState.HourState> {
48 return document.select(".weather-strip--detail").mapIndexed { index, element -> parseHourState(index, element) }
51 private fun parseHourState(index: Int, hourElement: Element): WetterComState.HourState {
52 return WetterComState.HourState.atHour(index)
53 .temperature(parseTemperature(hourElement))
54 .rainProbability(parseRainProbability(hourElement))
55 .rainAmount(parseRainAmount(hourElement))
56 .windFrom(parseWindDirection(hourElement))
57 .at(parseWindSpeed(hourElement))
58 .describedAs(parseDescription(hourElement))
59 .withImage(parseImageUrl(hourElement))
63 private fun parseTemperature(hourElement: Element) =
64 hourElement.extractText(".weather-strip__2 .item-weathericon .palm-hide").filter { (it >= '0') and (it <= '9') }.toDouble()
66 private fun parseRainProbability(hourElement: Element) =
67 hourElement.extractText(".weather-strip__3 .text--left:eq(0) .flag__body span:eq(0)").filter { (it >= '0') and (it <= '9') }.toDouble() / 100.0
69 private fun parseRainAmount(hourElement: Element) =
70 hourElement.extractText(".weather-strip__3 .text--left:eq(0) .flag__body span:eq(1)").trim().split(" ")[1].toDouble()
72 private fun parseWindDirection(hourElement: Element) =
73 hourElement.extractText(".weather-strip__3 .text--left:eq(1) .flag__body span:eq(0)").trim().split(",")[0].toWindDirection()
75 private fun parseWindSpeed(hourElement: Element) =
76 hourElement.extractText(".weather-strip__3 .text--left:eq(1) .flag__body span:eq(0)").split(Regex("[, ]+"))[1].toDouble()
78 private fun parseDescription(hourElement: Element) =
79 hourElement.extractText(".weather-strip__1 .vhs-text--small")
81 private fun parseImageUrl(hourElement: Element) =
82 hourElement.select(".weather-strip__2 img").firstOrNull()?.attr("src") ?: ""
84 private fun Element.extractText(selector: String) = select(selector).firstOrNull()?.text() ?: ""