dc33b659cd319ba22831e27e4e66bf6bb36db1fa
[rhynodge.git] / src / main / kotlin / net / pterodactylus / rhynodge / webpages / weather / wettercom / WetterComFilter.kt
1 package net.pterodactylus.rhynodge.webpages.weather.wettercom
2
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 net.pterodactylus.rhynodge.webpages.weather.toWindDirection
8 import org.jsoup.nodes.Document
9 import org.jsoup.nodes.Element
10 import java.time.LocalDateTime
11 import java.time.ZoneId.of
12 import java.time.ZonedDateTime
13 import java.time.format.DateTimeFormatter
14
15 /**
16  * HTML parser for [wetter.com](https://www.wetter.com/).
17  *
18  * @author [David ‘Bombe’ Roden](mailto:bombe@pterodactylus.net)
19  */
20 class WetterComFilter : Filter {
21
22     private val dateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
23
24     override fun filter(state: State): State {
25         if (state.success().not()) {
26             return FailedState.from(state)
27         }
28         if (state !is HtmlState) {
29             throw IllegalArgumentException("")
30         }
31
32         return parseWetterComState(state)
33     }
34
35     private fun parseWetterComState(state: HtmlState): State {
36         val dateTime = parseDateTime(state.document()) ?: return FailedState(IllegalArgumentException("no date present"))
37         val wetterComState = WetterComState(dateTime)
38         parseHourStates(state.document()).forEach { wetterComState.addHour(it) }
39         return wetterComState
40     }
41
42     private fun parseDateTime(document: Document): ZonedDateTime? {
43         val dateElement = document.select("#furtherDetails .portable-mb h3")
44                 .single()?.text()?.split(",")?.get(1)?.trim() ?: return null
45         val timeElement = document.select(".weather-strip--detail .delta.palm-hide")
46                 .first()?.text()?.split(" ")?.first() ?: return null
47         return LocalDateTime.from(dateTimeFormatter.parse("%s %s".format(dateElement, timeElement))).atZone(of("Europe/Berlin"))
48     }
49
50     private fun parseHourStates(document: Document): List<HourState> {
51         return document.select(".weather-strip--detail").mapIndexed { index, element -> parseHourState(index, element) }
52     }
53
54     private fun parseHourState(index: Int, hourElement: Element): HourState {
55         return HourState.atHour(index)
56                 .temperature(parseTemperature(hourElement))
57                 .rainProbability(parseRainProbability(hourElement))
58                 .rainAmount(parseRainAmount(hourElement))
59                 .windFrom(parseWindDirection(hourElement))
60                 .at(parseWindSpeed(hourElement))
61                 .describedAs(parseDescription(hourElement))
62                 .withImage(parseImageUrl(hourElement))
63                 .build()
64     }
65
66     private fun parseTemperature(hourElement: Element) =
67             hourElement.extractText(".weather-strip__2 .item-weathericon .palm-hide").filter { (it >= '0') and (it <= '9') }.toDouble()
68
69     private fun parseRainProbability(hourElement: Element) =
70             hourElement.extractText(".weather-strip__3 .text--left:eq(0) .flag__body span:eq(0)").filter { (it >= '0') and (it <= '9') }.toDouble() / 100.0
71
72     private fun parseRainAmount(hourElement: Element) =
73             hourElement.extractText(".weather-strip__3 .text--left:eq(0) .flag__body span:eq(1)").trim().split(" ").getOrNull(1)?.toDouble() ?: 0.0
74
75     private fun parseWindDirection(hourElement: Element) =
76             hourElement.extractText(".weather-strip__3 .text--left:eq(1) .flag__body span:eq(0)").trim().split(",")[0].toWindDirection()
77
78     private fun parseWindSpeed(hourElement: Element) =
79             hourElement.extractText(".weather-strip__3 .text--left:eq(1) .flag__body span:eq(0)").split(Regex("[, ]+"))[1].toDouble()
80
81     private fun parseDescription(hourElement: Element) =
82             hourElement.extractText(".weather-strip__1 .vhs-text--small")
83
84     private fun parseImageUrl(hourElement: Element) =
85             hourElement.select(".weather-strip__2 img").firstOrNull()?.attr("src") ?: ""
86
87     private fun Element.extractText(selector: String) = select(selector).firstOrNull()?.text() ?: ""
88
89 }