9df926ffa0d8d003eb01cf357a5330dfd235b4eb
[rhynodge.git] / src / main / kotlin / net / pterodactylus / rhynodge / webpages / weather / wetterde / WetterDeFilter.kt
1 package net.pterodactylus.rhynodge.webpages.weather.wetterde
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.DateTimeException
14 import java.time.LocalDate
15 import java.time.LocalTime
16 import java.time.ZoneId
17 import java.time.ZonedDateTime
18 import java.time.format.DateTimeFormatter
19 import java.util.Locale
20
21 /**
22  * HTML parser for [wetter.de](https://www.wetter.de/).
23  *
24  * @author [David ‘Bombe’ Roden](mailto:bombe@pterodactylus.net)
25  */
26 class WetterDeFilter : Filter {
27
28     object DateParser {
29         val parser = DateTimeFormatter.ofPattern("dd. MMMM yyyy").withLocale(Locale.GERMAN).withZone(ZoneId.of("Europe/Berlin"))
30     }
31
32     override fun filter(state: State): State {
33         if (state.success().not()) {
34             return FailedState.from(state)
35         }
36         state as? HtmlState ?: throw IllegalArgumentException("state is not an HTML state")
37
38         return parseWetterDeState(state)
39     }
40
41     private fun parseWetterDeState(htmlState: HtmlState): WeatherState {
42         val dateTime = parseDate(htmlState.document()) ?: throw IllegalArgumentException("date can not be parsed")
43         val wetterDeState = WeatherState("wetter.de", dateTime)
44         parseHourStates(htmlState.document()).forEach { wetterDeState += it }
45         return wetterDeState
46     }
47
48     private fun parseDate(document: Document): ZonedDateTime? {
49         return document.select(".forecast-detail-headline").text()
50                 .extract(Regex(".*?([0-9]{1,2}\\. [^ ]+ [0-9]{4}).*?$"))?.toDate() ?: return null
51     }
52
53     private fun String.extract(regex: Regex): String? {
54         return regex.find(this)?.groups?.get(1)?.value
55     }
56
57     private fun String.toDate(): ZonedDateTime? {
58         return try {
59             ZonedDateTime.of(DateParser.parser.parse(this, LocalDate::from), LocalTime.of(0, 0), ZoneId.of("Europe/Berlin"))
60         } catch (e: DateTimeException) {
61             e.printStackTrace()
62             null
63         }
64     }
65
66     private fun parseHourStates(document: Document): Collection<HourState> {
67         return document.select(".forecast-detail-column-1h").mapIndexed { index, element -> parseHourState(index, element) }
68     }
69
70     private fun parseHourState(index: Int, element: Element): HourState {
71         return HourState(
72                 index,
73                 element.select("span.temperature").text().extract(Regex("([0-9]+).*$"))?.toInt() ?: 0,
74                 element.select("span.temperature-wind-chill").text().extract(Regex("[^0-9]*?([0-9]+)[^0-9]*?"))?.toInt() ?: 0,
75                 (element.select(".forecast-icon-rain-fill").attr("style").extract(Regex("[^0-9]*?([0-9]+)[^0-9]*?"))?.toInt() ?: 0) / 100.0,
76                 element.select(".forecast-rain span:eq(4)").text().replace(',', '.').extract(Regex("[^0-9]*?([0-9\\.]+)[^0-9]*?"))?.toDouble() ?: 0.0,
77                 element.select(".forecast-wind-text").text().extract(Regex(".*?(Nord((nord)?(ost|west))?|Ost((nord|süd)ost)?|Süd((süd)?(ost|west))?|West((nord|süd)west)?).*?"))?.toWindDirection() ?: WindDirection.NONE,
78                 element.select(".forecast-wind-text span:eq(1)").text().extract(Regex(".*?([0-9]+) km/h.*?"))?.toInt() ?: 0,
79                 element.select(".forecast-wind-text span:eq(4)").text().extract(Regex(".*?([0-9]+) km/h.*?"))?.toInt() ?: 0,
80                 (element.select(".forecast-humidity-text span:eq(0)").text().extract(Regex("([0-9]+)[^0-9]*?"))?.toInt() ?: 0) / 100.0,
81                 element.select("span.temperature-condition").text(),
82                 element.select(".forecast-image img").attr("src").prefixProtocol()
83         )
84     }
85
86     private fun String.prefixProtocol(): String {
87         return if (startsWith("//")) "http:" + this else this
88     }
89
90 }