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