From bb61e4abce5cbbeb142d2570e4dddbf41db91e34 Mon Sep 17 00:00:00 2001 From: Mario Schmidt Date: Tue, 7 Jan 2020 00:22:48 +0100 Subject: [PATCH 1/7] Plugin for wetteronline.de * Uses API for the free HTML widget (basic 3 days forecast) --- wetteronline/WetterOnline/Pipfile | 14 +++++ wetteronline/WetterOnline/__init__.py | 75 +++++++++++++++++++++++++++ wetteronline/__init__.py | 39 ++++++++++++++ wetteronline/config.md | 9 ++++ wetteronline/item.md | 23 ++++++++ wetteronline/plugin.conf | 2 + 6 files changed, 162 insertions(+) create mode 100644 wetteronline/WetterOnline/Pipfile create mode 100644 wetteronline/WetterOnline/__init__.py create mode 100644 wetteronline/__init__.py create mode 100644 wetteronline/config.md create mode 100644 wetteronline/item.md create mode 100644 wetteronline/plugin.conf diff --git a/wetteronline/WetterOnline/Pipfile b/wetteronline/WetterOnline/Pipfile new file mode 100644 index 0000000..c98f658 --- /dev/null +++ b/wetteronline/WetterOnline/Pipfile @@ -0,0 +1,14 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] +pylint = "*" + +[packages] +requests = "*" +lxml = "*" + +[requires] + diff --git a/wetteronline/WetterOnline/__init__.py b/wetteronline/WetterOnline/__init__.py new file mode 100644 index 0000000..7766e67 --- /dev/null +++ b/wetteronline/WetterOnline/__init__.py @@ -0,0 +1,75 @@ +import logging +import urllib.parse +import requests + +from lxml import html + +class WetterOnline: + def __init__(self, location): + self.logger = logging.getLogger(__name__) + self.api = 'http://api.wetteronline.de' + self.location = None + self.weather = None + self.url = self.get_url(location) + + def get_url(self, location): + try: + url = '{}/search?name={}'.format(self.api, urllib.parse.quote(location)) + rq = self._fetch_data(url, headers={'content-type': 'application/json'}) + res = rq.json() + except Exception: + self.logger.exception('Failed searching location %s', location) + + for x in res: + if 'match' in x and x['match'] == 'yes': + self.location = res[0] + + if not self.location: + raise Exception("Location not found!") + + return '{}/wetterwidget?gid={}&modeid={}&locationname={}'.format(self.api, self.location['geoID'], 'FC3', self.location['locationName']) + + def get(self): + try: + rq = self._fetch_data(self.url) + tree = html.fromstring(rq.content) + weather = self._parse_tree(tree) + except Exception: + self.logger.exception('Failed getting weather') + + return { + 'location': self.location, + 'weather': weather + } + + def _fetch_data(self, url, headers=None): + try: + self.logger.debug('Fetching: %s', url) + rq = requests.get(url, headers=headers, verify=False) + except Exception: + self.logger.exception('Failed fetching data') + return + return rq + + def _parse_tree(self, tree): + weather = [] + try: + for day in tree.xpath('//div[@class="forecast_day"]'): + w = { + 'day': day.xpath('.//div[1]/text()')[0], + 'date': day.xpath('.//div[2]/text()')[0], + 'temp_max': float(day.xpath('.//div[4]/text()')[0].replace('°', '')), + 'temp_min': float(day.xpath('.//div[5]/text()')[0].replace('°', '')), + 'sunhours': float(day.xpath('.//div[8]/text()')[0].replace('h', '')), + 'rain_probability': float(day.xpath('.//div[9]/text()')[0].replace('%', '')), + 'img': day.xpath('.//div[@class="weathersymbol"]/img/@src')[0], + } + + if w['day'] == 'heute': + w['temp_now'] = float(tree.xpath('//div[@id="temperature"]/text()')[0].replace('°', '')) + + weather.append(w) + except Exception: + self.logger.exception('Failed parsing data') + return + return weather diff --git a/wetteronline/__init__.py b/wetteronline/__init__.py new file mode 100644 index 0000000..2f0ce92 --- /dev/null +++ b/wetteronline/__init__.py @@ -0,0 +1,39 @@ +import lib.plugin + +from wetteronline.WetterOnline import WetterOnline + +class WetterOnlinePlugin(lib.plugin.Plugin): + def __init__(self, core, conf): + lib.plugin.Plugin.__init__(self, core, conf) + self.location = '{}'.format(conf.get('location')) + self.items = {} + core.scheduler.add('_wetteronline', self.update, cycle=int(conf.get('cycle', 900))) + + def start(self): + self.wol = WetterOnline(self.location) + self.alive = True + + def stop(self): + self.alive = False + + def pre_stage(self): + for node in self._core.config.query_nodes('wetteronline'): + keyword = node.attr['wetteronline'] + self.items[keyword] = node + + def update(self, value=None, trigger=None): + try: + wol_data = self.wol.get() + self.logger.info("Successfully fetched WetterOnline data") + except Exception: + self.logger.exception('Failed getting WetterOnline data') + return + + for keyword, item in self.items.items(): + try: + (wol_idx, wol_key) = keyword.split('__') + value = wol_data['weather'][int(wol_idx)][wol_key] + item(value, trigger=self.get_trigger()) + except: + self.logger.exception('Unable to update item %s with value %s' % (item, value)) + diff --git a/wetteronline/config.md b/wetteronline/config.md new file mode 100644 index 0000000..807c302 --- /dev/null +++ b/wetteronline/config.md @@ -0,0 +1,9 @@ +{{ + +form.guiInput('location', label='Ort', required=True, value='Berlin', help="""Ortsname wie er für das HTML-Widget von WetterOnline funktioniert (https://www.wetteronline.de/wetter-widget)""") + +select_cycle = oDict([('60', '1 Minute'), ('300', '5 Minuten'), ('900', '15 Minuten'), ('1800', '30 Minuten'), ('3600', '1 Stunde')]) +form.guiSelect('cycle', label='Abfrageintervall', named=select_cycle) + +}} + diff --git a/wetteronline/item.md b/wetteronline/item.md new file mode 100644 index 0000000..26898ed --- /dev/null +++ b/wetteronline/item.md @@ -0,0 +1,23 @@ +{{ select_data = oDict([ + ('', ''), + ('_0', '== Heute =='), + ('0__temp_now', 'Temperatur aktuell (°C)'), + ('0__temp_min', 'Temperatur min. (°C)'), + ('0__temp_max', 'Temperatur max. (°C)'), + ('0__sunhours', 'Sonnenstunden (h)'), + ('0__rain_probability', 'Regenwahrscheinlichkeit (%)'), + + ('_1', '== Morgen =='), + ('1__temp_min', 'Temperatur min. (°C)'), + ('1__temp_max', 'Temperatur max. (°C)'), + ('1__sunhours', 'Sonnenstunden (h)'), + ('1__rain_probability', 'Regenwahrscheinlichkeit (%)'), + + ('_2', '== Übermorgen =='), + ('2__temp_min', 'Temperatur min. (°C)'), + ('2__temp_max', 'Temperatur max. (°C)'), + ('2__sunhours', 'Sonnenstunden (h)'), + ('2__rain_probability', 'Regenwahrscheinlichkeit (%)'), +]) +form.guiSelect('wetteronline', label='Wert', named=select_data) +}} \ No newline at end of file diff --git a/wetteronline/plugin.conf b/wetteronline/plugin.conf new file mode 100644 index 0000000..b073ee6 --- /dev/null +++ b/wetteronline/plugin.conf @@ -0,0 +1,2 @@ +[WetterOnline] + class = WetterOnlinePlugin From ad0f0607d702afbf2c3ebaba9af2d0dcf5de0a1a Mon Sep 17 00:00:00 2001 From: Mario Schmidt Date: Wed, 8 Jan 2020 01:46:16 +0100 Subject: [PATCH 2/7] Add items for weather text and icon * Sadly, the Icon widget doesn't work properly in 'dynamic' mode, you must use 'map' mode and specify all icons manually --- wetteronline/WetterOnline/__init__.py | 1 + wetteronline/__init__.py | 55 ++++++++++++++++++++++++++- wetteronline/item.md | 8 +++- 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/wetteronline/WetterOnline/__init__.py b/wetteronline/WetterOnline/__init__.py index 7766e67..0ff4931 100644 --- a/wetteronline/WetterOnline/__init__.py +++ b/wetteronline/WetterOnline/__init__.py @@ -63,6 +63,7 @@ def _parse_tree(self, tree): 'sunhours': float(day.xpath('.//div[8]/text()')[0].replace('h', '')), 'rain_probability': float(day.xpath('.//div[9]/text()')[0].replace('%', '')), 'img': day.xpath('.//div[@class="weathersymbol"]/img/@src')[0], + 'title': day.xpath('.//div[@class="weathersymbol"]/img/@title')[0], } if w['day'] == 'heute': diff --git a/wetteronline/__init__.py b/wetteronline/__init__.py index 2f0ce92..c5bf129 100644 --- a/wetteronline/__init__.py +++ b/wetteronline/__init__.py @@ -1,7 +1,51 @@ import lib.plugin - +import re +from pprint import pformat from wetteronline.WetterOnline import WetterOnline +icons = { + 'so': 'sun', + 'mo': 'moon', + 'ns': 'weather.fog', + 'nm': 'weather.fog', + 'nb': 'weather.fog', + 'wb': 'weather.partlysunny', + 'mb': 'weather.partlysunny_n', + 'bd': 'weather.cloudy', + 'wbs1': 'weather.partlysunny', + 'mbs1': 'weather.partlysunny_n', + 'bdr1': 'weather.rain1', + 'wbs2': 'weather.rain2_shower', + 'mbs2': 'weather.rain2', + 'bdr2': 'weather.rain2', + 'bdr3': 'weather.rain3', + 'wbsrs1': 'weather.snowrain2_shower', + 'mbsrs1': 'weather.snowrain1', + 'bdsr1': 'weather.snowrain1', + 'wbsrs2': 'weather.snowrain3_shower', + 'mbsrs2': 'weather.snowrain2', + 'bdsr2': 'weather.snowrain2', + 'bdsr3': 'weather.snowrain3', + 'wbsns1': 'weather.snow2_shower', + 'mbsns1': 'weather.snow1', + 'bdsn1': 'weather.snow1', + 'wbsns2': 'weather.snow3_shower', + 'mbsns2': 'weather.snow2', + 'bdsn2': 'weather.snow2', + 'bdsn3': 'weather.snow3', + 'wbsg': 'weather.storm1', + 'mbsg': 'weather.storm1', + 'bdsg': 'weather.storm1', + 'wbg1': 'weather.storm1', + 'mbg1': 'weather.storm1', + 'bdg1': 'weather.storm1', + 'wbg2': 'weather.storm2', + 'mbg2': 'weather.storm2', + 'bdg2': 'weather.storm2', + 'bdgr1': 'weather.graupel2', + 'bdgr2': 'weather.graupel3', +} + class WetterOnlinePlugin(lib.plugin.Plugin): def __init__(self, core, conf): lib.plugin.Plugin.__init__(self, core, conf) @@ -28,6 +72,15 @@ def update(self, value=None, trigger=None): except Exception: self.logger.exception('Failed getting WetterOnline data') return + + # Map native icons + for wd in wol_data['weather']: + img = re.search(r"/([^/_]+)_+\.svg$", wd['img']).group(1) + if img in icons: + wd['img'] = icons[img] + else: + wd['img'] = 'question' + for keyword, item in self.items.items(): try: diff --git a/wetteronline/item.md b/wetteronline/item.md index 26898ed..02400d1 100644 --- a/wetteronline/item.md +++ b/wetteronline/item.md @@ -1,19 +1,25 @@ {{ select_data = oDict([ ('', ''), ('_0', '== Heute =='), + ('0__title', 'Wetterlage'), + ('0__img', 'Icon'), ('0__temp_now', 'Temperatur aktuell (°C)'), ('0__temp_min', 'Temperatur min. (°C)'), ('0__temp_max', 'Temperatur max. (°C)'), ('0__sunhours', 'Sonnenstunden (h)'), ('0__rain_probability', 'Regenwahrscheinlichkeit (%)'), - ('_1', '== Morgen =='), + ('_1', '== Morgen =='), + ('1__title', 'Wetterlage'), + ('1__img', 'Icon'), ('1__temp_min', 'Temperatur min. (°C)'), ('1__temp_max', 'Temperatur max. (°C)'), ('1__sunhours', 'Sonnenstunden (h)'), ('1__rain_probability', 'Regenwahrscheinlichkeit (%)'), ('_2', '== Übermorgen =='), + ('2__title', 'Wetterlage'), + ('2__img', 'Icon'), ('2__temp_min', 'Temperatur min. (°C)'), ('2__temp_max', 'Temperatur max. (°C)'), ('2__sunhours', 'Sonnenstunden (h)'), From 2aa57ccdccd5f44be580575692ba30a282bdba0a Mon Sep 17 00:00:00 2001 From: Mario Schmidt Date: Thu, 1 Oct 2020 11:58:59 +0200 Subject: [PATCH 3/7] Add some missing icons for rain showers --- wetteronline/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wetteronline/__init__.py b/wetteronline/__init__.py index c5bf129..c9d504d 100644 --- a/wetteronline/__init__.py +++ b/wetteronline/__init__.py @@ -15,10 +15,13 @@ 'wbs1': 'weather.partlysunny', 'mbs1': 'weather.partlysunny_n', 'bdr1': 'weather.rain1', + 'wbr1': 'weather.rain2_shower', + 'wbr2': 'weather.rain2_shower', 'wbs2': 'weather.rain2_shower', 'mbs2': 'weather.rain2', 'bdr2': 'weather.rain2', 'bdr3': 'weather.rain3', + 'wbr3': 'weather.rain3_shower', 'wbsrs1': 'weather.snowrain2_shower', 'mbsrs1': 'weather.snowrain1', 'bdsr1': 'weather.snowrain1', From fb29b7240687459ff2f41e76c55f3a3716cb98d4 Mon Sep 17 00:00:00 2001 From: Mario Schmidt Date: Fri, 4 Dec 2020 12:13:36 +0100 Subject: [PATCH 4/7] Fix icon regex and add 'src' attribute for original icon URL * Can be used to add missing icon mappings --- wetteronline/WetterOnline/__init__.py | 3 ++- wetteronline/__init__.py | 11 +++++------ wetteronline/item.md | 7 +++++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/wetteronline/WetterOnline/__init__.py b/wetteronline/WetterOnline/__init__.py index 0ff4931..80cd7e0 100644 --- a/wetteronline/WetterOnline/__init__.py +++ b/wetteronline/WetterOnline/__init__.py @@ -62,7 +62,8 @@ def _parse_tree(self, tree): 'temp_min': float(day.xpath('.//div[5]/text()')[0].replace('°', '')), 'sunhours': float(day.xpath('.//div[8]/text()')[0].replace('h', '')), 'rain_probability': float(day.xpath('.//div[9]/text()')[0].replace('%', '')), - 'img': day.xpath('.//div[@class="weathersymbol"]/img/@src')[0], + 'src': day.xpath('.//div[@class="weathersymbol"]/img/@src')[0], + 'img': 'question', 'title': day.xpath('.//div[@class="weathersymbol"]/img/@title')[0], } diff --git a/wetteronline/__init__.py b/wetteronline/__init__.py index c9d504d..01c1682 100644 --- a/wetteronline/__init__.py +++ b/wetteronline/__init__.py @@ -78,12 +78,11 @@ def update(self, value=None, trigger=None): # Map native icons for wd in wol_data['weather']: - img = re.search(r"/([^/_]+)_+\.svg$", wd['img']).group(1) - if img in icons: - wd['img'] = icons[img] - else: - wd['img'] = 'question' - + img = re.search(r"/([^/_]+)_*\.svg$", wd['src']) + if img: + img = img.group(1) + if img in icons: + wd['img'] = icons[img] for keyword, item in self.items.items(): try: diff --git a/wetteronline/item.md b/wetteronline/item.md index 02400d1..4f69ecc 100644 --- a/wetteronline/item.md +++ b/wetteronline/item.md @@ -3,6 +3,7 @@ ('_0', '== Heute =='), ('0__title', 'Wetterlage'), ('0__img', 'Icon'), + ('0__src', 'Icon (orig)'), ('0__temp_now', 'Temperatur aktuell (°C)'), ('0__temp_min', 'Temperatur min. (°C)'), ('0__temp_max', 'Temperatur max. (°C)'), @@ -11,7 +12,8 @@ ('_1', '== Morgen =='), ('1__title', 'Wetterlage'), - ('1__img', 'Icon'), + ('1__img', 'Icon'), + ('1__src', 'Icon (orig)'), ('1__temp_min', 'Temperatur min. (°C)'), ('1__temp_max', 'Temperatur max. (°C)'), ('1__sunhours', 'Sonnenstunden (h)'), @@ -20,10 +22,11 @@ ('_2', '== Übermorgen =='), ('2__title', 'Wetterlage'), ('2__img', 'Icon'), + ('2__src', 'Icon (orig)'), ('2__temp_min', 'Temperatur min. (°C)'), ('2__temp_max', 'Temperatur max. (°C)'), ('2__sunhours', 'Sonnenstunden (h)'), ('2__rain_probability', 'Regenwahrscheinlichkeit (%)'), ]) form.guiSelect('wetteronline', label='Wert', named=select_data) -}} \ No newline at end of file +}} From 796a05838d0cf14b0800e8f9eced4ae3f1b2c15a Mon Sep 17 00:00:00 2001 From: Mario Schmidt Date: Fri, 17 Feb 2023 17:07:15 +0100 Subject: [PATCH 5/7] Adapt to changed API (Use main website instead) --- wetteronline/WetterOnline/__init__.py | 37 +++++++++++++++------------ wetteronline/__init__.py | 5 ++-- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/wetteronline/WetterOnline/__init__.py b/wetteronline/WetterOnline/__init__.py index 80cd7e0..ff7b862 100644 --- a/wetteronline/WetterOnline/__init__.py +++ b/wetteronline/WetterOnline/__init__.py @@ -1,5 +1,6 @@ import logging import urllib.parse +import re import requests from lxml import html @@ -7,27 +8,26 @@ class WetterOnline: def __init__(self, location): self.logger = logging.getLogger(__name__) - self.api = 'http://api.wetteronline.de' self.location = None self.weather = None self.url = self.get_url(location) def get_url(self, location): try: - url = '{}/search?name={}'.format(self.api, urllib.parse.quote(location)) + url = 'http://api.wetteronline.de/search?name={}'.format(urllib.parse.quote(location.lower())) rq = self._fetch_data(url, headers={'content-type': 'application/json'}) res = rq.json() except Exception: self.logger.exception('Failed searching location %s', location) - + for x in res: if 'match' in x and x['match'] == 'yes': - self.location = res[0] + self.location = res[0]['geoName'] if not self.location: raise Exception("Location not found!") - return '{}/wetterwidget?gid={}&modeid={}&locationname={}'.format(self.api, self.location['geoID'], 'FC3', self.location['locationName']) + return 'http://www.wetteronline.de/{}/'.format(self.location) def get(self): try: @@ -54,21 +54,24 @@ def _fetch_data(self, url, headers=None): def _parse_tree(self, tree): weather = [] try: - for day in tree.xpath('//div[@class="forecast_day"]'): + for day in range(1, 4): + daystr = tree.xpath(f'//table[@id="daterow"]/tbody/tr/th[{day}]/text()')[-1].strip() + # Workaround 'today' becoming 'tomorrow' too early on the site + if day == 1 and daystr.lower() != 'heute': + continue w = { - 'day': day.xpath('.//div[1]/text()')[0], - 'date': day.xpath('.//div[2]/text()')[0], - 'temp_max': float(day.xpath('.//div[4]/text()')[0].replace('°', '')), - 'temp_min': float(day.xpath('.//div[5]/text()')[0].replace('°', '')), - 'sunhours': float(day.xpath('.//div[8]/text()')[0].replace('h', '')), - 'rain_probability': float(day.xpath('.//div[9]/text()')[0].replace('%', '')), - 'src': day.xpath('.//div[@class="weathersymbol"]/img/@src')[0], + 'day': daystr, + 'date': re.search(r"(\d+\.\d+\.)", tree.xpath(f'//table[@id="daterow"]/tbody/tr/th[{day}]/span/text()')[-1].strip()).group(0), + 'temp_max': float(tree.xpath(f'//table[@id="weather"]/tbody/tr[@class="Maximum Temperature"]/td[{day}]/div/span[2]/text()')[-1].replace('°', '')), + 'temp_min': float(tree.xpath(f'//table[@id="weather"]/tbody/tr[@class="Minimum Temperature"]/td[{day}]/div/span[2]/text()')[-1].replace('°', '')), + 'sunhours': float(re.search(r"(\d+)", tree.xpath(f'//tr[@id="sun_teaser"]/td[{day}]/span[1]/text()')[-1]).group(0)), + 'rain_probability': float(re.search(r"(\d+)", tree.xpath(f'//tr[@id="precipitation_teaser"]/td[{day}]/span[1]/text()')[-1]).group(0)), + 'src': tree.xpath(f'//tr[@id="wwdaysymbolrow"]/td[{day}]/img/@src')[-1], 'img': 'question', - 'title': day.xpath('.//div[@class="weathersymbol"]/img/@title')[0], + 'title': tree.xpath(f'//tr[@id="wwdaysymbolrow"]/td[{day}]/@data-tt-args')[-1].split(',')[2].replace('"', ''), } - - if w['day'] == 'heute': - w['temp_now'] = float(tree.xpath('//div[@id="temperature"]/text()')[0].replace('°', '')) + if day == 1: + w['temp_now'] = float(tree.xpath('//div[@id="nowcast-card-temperature"]/div[1]/text()')[-1]) weather.append(w) except Exception: diff --git a/wetteronline/__init__.py b/wetteronline/__init__.py index 01c1682..c8a0f68 100644 --- a/wetteronline/__init__.py +++ b/wetteronline/__init__.py @@ -15,6 +15,7 @@ 'wbs1': 'weather.partlysunny', 'mbs1': 'weather.partlysunny_n', 'bdr1': 'weather.rain1', + 'bds1': 'weather.rain1', 'wbr1': 'weather.rain2_shower', 'wbr2': 'weather.rain2_shower', 'wbs2': 'weather.rain2_shower', @@ -71,14 +72,14 @@ def pre_stage(self): def update(self, value=None, trigger=None): try: wol_data = self.wol.get() - self.logger.info("Successfully fetched WetterOnline data") + self.logger.info("Successfully fetched WetterOnline data for %i days", len(wol_data['weather'])) except Exception: self.logger.exception('Failed getting WetterOnline data') return # Map native icons for wd in wol_data['weather']: - img = re.search(r"/([^/_]+)_*\.svg$", wd['src']) + img = re.search(r"/([^/_]+)_*\.(svg|png)$", wd['src']) if img: img = img.group(1) if img in icons: From b9d42aa1b4f5f56a455628d272dee1a90d6b30fa Mon Sep 17 00:00:00 2001 From: Mario Schmidt Date: Tue, 21 Feb 2023 09:14:48 +0100 Subject: [PATCH 6/7] Blackify --- wetteronline/WetterOnline/__init__.py | 98 ++++++++++----- wetteronline/__init__.py | 167 ++++++++++++++------------ 2 files changed, 155 insertions(+), 110 deletions(-) diff --git a/wetteronline/WetterOnline/__init__.py b/wetteronline/WetterOnline/__init__.py index ff7b862..2a55684 100644 --- a/wetteronline/WetterOnline/__init__.py +++ b/wetteronline/WetterOnline/__init__.py @@ -5,6 +5,7 @@ from lxml import html + class WetterOnline: def __init__(self, location): self.logger = logging.getLogger(__name__) @@ -14,20 +15,22 @@ def __init__(self, location): def get_url(self, location): try: - url = 'http://api.wetteronline.de/search?name={}'.format(urllib.parse.quote(location.lower())) - rq = self._fetch_data(url, headers={'content-type': 'application/json'}) + url = "http://api.wetteronline.de/search?name={}".format( + urllib.parse.quote(location.lower()) + ) + rq = self._fetch_data(url, headers={"content-type": "application/json"}) res = rq.json() except Exception: - self.logger.exception('Failed searching location %s', location) + self.logger.exception("Failed searching location %s", location) for x in res: - if 'match' in x and x['match'] == 'yes': - self.location = res[0]['geoName'] + if "match" in x and x["match"] == "yes": + self.location = res[0]["geoName"] if not self.location: raise Exception("Location not found!") - return 'http://www.wetteronline.de/{}/'.format(self.location) + return "http://www.wetteronline.de/{}/".format(self.location) def get(self): try: @@ -35,46 +38,81 @@ def get(self): tree = html.fromstring(rq.content) weather = self._parse_tree(tree) except Exception: - self.logger.exception('Failed getting weather') + self.logger.exception("Failed getting weather") - return { - 'location': self.location, - 'weather': weather - } + return {"location": self.location, "weather": weather} def _fetch_data(self, url, headers=None): try: - self.logger.debug('Fetching: %s', url) + self.logger.debug("Fetching: %s", url) rq = requests.get(url, headers=headers, verify=False) except Exception: - self.logger.exception('Failed fetching data') + self.logger.exception("Failed fetching data") return return rq def _parse_tree(self, tree): weather = [] try: - for day in range(1, 4): - daystr = tree.xpath(f'//table[@id="daterow"]/tbody/tr/th[{day}]/text()')[-1].strip() - # Workaround 'today' becoming 'tomorrow' too early on the site - if day == 1 and daystr.lower() != 'heute': - continue + for day in range(1, 4): + daystr = tree.xpath( + f'//table[@id="daterow"]/tbody/tr/th[{day}]/text()' + )[-1].strip() + # Workaround 'today' becoming 'tomorrow' too early on the site + if day == 1 and daystr.lower() != "heute": + continue w = { - 'day': daystr, - 'date': re.search(r"(\d+\.\d+\.)", tree.xpath(f'//table[@id="daterow"]/tbody/tr/th[{day}]/span/text()')[-1].strip()).group(0), - 'temp_max': float(tree.xpath(f'//table[@id="weather"]/tbody/tr[@class="Maximum Temperature"]/td[{day}]/div/span[2]/text()')[-1].replace('°', '')), - 'temp_min': float(tree.xpath(f'//table[@id="weather"]/tbody/tr[@class="Minimum Temperature"]/td[{day}]/div/span[2]/text()')[-1].replace('°', '')), - 'sunhours': float(re.search(r"(\d+)", tree.xpath(f'//tr[@id="sun_teaser"]/td[{day}]/span[1]/text()')[-1]).group(0)), - 'rain_probability': float(re.search(r"(\d+)", tree.xpath(f'//tr[@id="precipitation_teaser"]/td[{day}]/span[1]/text()')[-1]).group(0)), - 'src': tree.xpath(f'//tr[@id="wwdaysymbolrow"]/td[{day}]/img/@src')[-1], - 'img': 'question', - 'title': tree.xpath(f'//tr[@id="wwdaysymbolrow"]/td[{day}]/@data-tt-args')[-1].split(',')[2].replace('"', ''), + "day": daystr, + "date": re.search( + r"(\d+\.\d+\.)", + tree.xpath( + f'//table[@id="daterow"]/tbody/tr/th[{day}]/span/text()' + )[-1].strip(), + ).group(0), + "temp_max": float( + tree.xpath( + f'//table[@id="weather"]/tbody/tr[@class="Maximum Temperature"]/td[{day}]/div/span[2]/text()' + )[-1].replace("°", "") + ), + "temp_min": float( + tree.xpath( + f'//table[@id="weather"]/tbody/tr[@class="Minimum Temperature"]/td[{day}]/div/span[2]/text()' + )[-1].replace("°", "") + ), + "sunhours": float( + re.search( + r"(\d+)", + tree.xpath( + f'//tr[@id="sun_teaser"]/td[{day}]/span[1]/text()' + )[-1], + ).group(0) + ), + "rain_probability": float( + re.search( + r"(\d+)", + tree.xpath( + f'//tr[@id="precipitation_teaser"]/td[{day}]/span[1]/text()' + )[-1], + ).group(0) + ), + "src": tree.xpath(f'//tr[@id="wwdaysymbolrow"]/td[{day}]/img/@src')[ + -1 + ], + "img": "question", + "title": tree.xpath( + f'//tr[@id="wwdaysymbolrow"]/td[{day}]/@data-tt-args' + )[-1] + .split(",")[2] + .replace('"', ""), } if day == 1: - w['temp_now'] = float(tree.xpath('//div[@id="nowcast-card-temperature"]/div[1]/text()')[-1]) - + w["temp_now"] = float( + tree.xpath( + '//div[@id="nowcast-card-temperature"]/div[1]/text()' + )[-1] + ) weather.append(w) except Exception: - self.logger.exception('Failed parsing data') + self.logger.exception("Failed parsing data") return return weather diff --git a/wetteronline/__init__.py b/wetteronline/__init__.py index c8a0f68..1647829 100644 --- a/wetteronline/__init__.py +++ b/wetteronline/__init__.py @@ -4,92 +4,99 @@ from wetteronline.WetterOnline import WetterOnline icons = { - 'so': 'sun', - 'mo': 'moon', - 'ns': 'weather.fog', - 'nm': 'weather.fog', - 'nb': 'weather.fog', - 'wb': 'weather.partlysunny', - 'mb': 'weather.partlysunny_n', - 'bd': 'weather.cloudy', - 'wbs1': 'weather.partlysunny', - 'mbs1': 'weather.partlysunny_n', - 'bdr1': 'weather.rain1', - 'bds1': 'weather.rain1', - 'wbr1': 'weather.rain2_shower', - 'wbr2': 'weather.rain2_shower', - 'wbs2': 'weather.rain2_shower', - 'mbs2': 'weather.rain2', - 'bdr2': 'weather.rain2', - 'bdr3': 'weather.rain3', - 'wbr3': 'weather.rain3_shower', - 'wbsrs1': 'weather.snowrain2_shower', - 'mbsrs1': 'weather.snowrain1', - 'bdsr1': 'weather.snowrain1', - 'wbsrs2': 'weather.snowrain3_shower', - 'mbsrs2': 'weather.snowrain2', - 'bdsr2': 'weather.snowrain2', - 'bdsr3': 'weather.snowrain3', - 'wbsns1': 'weather.snow2_shower', - 'mbsns1': 'weather.snow1', - 'bdsn1': 'weather.snow1', - 'wbsns2': 'weather.snow3_shower', - 'mbsns2': 'weather.snow2', - 'bdsn2': 'weather.snow2', - 'bdsn3': 'weather.snow3', - 'wbsg': 'weather.storm1', - 'mbsg': 'weather.storm1', - 'bdsg': 'weather.storm1', - 'wbg1': 'weather.storm1', - 'mbg1': 'weather.storm1', - 'bdg1': 'weather.storm1', - 'wbg2': 'weather.storm2', - 'mbg2': 'weather.storm2', - 'bdg2': 'weather.storm2', - 'bdgr1': 'weather.graupel2', - 'bdgr2': 'weather.graupel3', + "so": "sun", + "mo": "moon", + "ns": "weather.fog", + "nm": "weather.fog", + "nb": "weather.fog", + "wb": "weather.partlysunny", + "mb": "weather.partlysunny_n", + "bd": "weather.cloudy", + "wbs1": "weather.partlysunny", + "mbs1": "weather.partlysunny_n", + "bdr1": "weather.rain1", + "bds1": "weather.rain1", + "wbr1": "weather.rain2_shower", + "wbr2": "weather.rain2_shower", + "wbs2": "weather.rain2_shower", + "mbs2": "weather.rain2", + "bdr2": "weather.rain2", + "bdr3": "weather.rain3", + "wbr3": "weather.rain3_shower", + "wbsrs1": "weather.snowrain2_shower", + "mbsrs1": "weather.snowrain1", + "bdsr1": "weather.snowrain1", + "wbsrs2": "weather.snowrain3_shower", + "mbsrs2": "weather.snowrain2", + "bdsr2": "weather.snowrain2", + "bdsr3": "weather.snowrain3", + "wbsns1": "weather.snow2_shower", + "mbsns1": "weather.snow1", + "bdsn1": "weather.snow1", + "wbsns2": "weather.snow3_shower", + "mbsns2": "weather.snow2", + "bdsn2": "weather.snow2", + "bdsn3": "weather.snow3", + "wbsg": "weather.storm1", + "mbsg": "weather.storm1", + "bdsg": "weather.storm1", + "wbg1": "weather.storm1", + "mbg1": "weather.storm1", + "bdg1": "weather.storm1", + "wbg2": "weather.storm2", + "mbg2": "weather.storm2", + "bdg2": "weather.storm2", + "bdgr1": "weather.graupel2", + "bdgr2": "weather.graupel3", } + class WetterOnlinePlugin(lib.plugin.Plugin): - def __init__(self, core, conf): - lib.plugin.Plugin.__init__(self, core, conf) - self.location = '{}'.format(conf.get('location')) - self.items = {} - core.scheduler.add('_wetteronline', self.update, cycle=int(conf.get('cycle', 900))) + def __init__(self, core, conf): + lib.plugin.Plugin.__init__(self, core, conf) + self.location = "{}".format(conf.get("location")) + self.items = {} + core.scheduler.add( + "_wetteronline", self.update, cycle=int(conf.get("cycle", 900)) + ) - def start(self): - self.wol = WetterOnline(self.location) - self.alive = True + def start(self): + self.wol = WetterOnline(self.location) + self.alive = True - def stop(self): - self.alive = False + def stop(self): + self.alive = False - def pre_stage(self): - for node in self._core.config.query_nodes('wetteronline'): - keyword = node.attr['wetteronline'] - self.items[keyword] = node + def pre_stage(self): + for node in self._core.config.query_nodes("wetteronline"): + keyword = node.attr["wetteronline"] + self.items[keyword] = node - def update(self, value=None, trigger=None): - try: - wol_data = self.wol.get() - self.logger.info("Successfully fetched WetterOnline data for %i days", len(wol_data['weather'])) - except Exception: - self.logger.exception('Failed getting WetterOnline data') - return + def update(self, value=None, trigger=None): + try: + wol_data = self.wol.get() + self.logger.info( + "Successfully fetched WetterOnline data for %i days", + len(wol_data["weather"]), + ) + except Exception: + self.logger.exception("Failed getting WetterOnline data") + return - # Map native icons - for wd in wol_data['weather']: - img = re.search(r"/([^/_]+)_*\.(svg|png)$", wd['src']) - if img: - img = img.group(1) - if img in icons: - wd['img'] = icons[img] - - for keyword, item in self.items.items(): - try: - (wol_idx, wol_key) = keyword.split('__') - value = wol_data['weather'][int(wol_idx)][wol_key] - item(value, trigger=self.get_trigger()) - except: - self.logger.exception('Unable to update item %s with value %s' % (item, value)) + # Map native icons + for wd in wol_data["weather"]: + img = re.search(r"/([^/_]+)_*\.(svg|png)$", wd["src"]) + if img: + img = img.group(1) + if img in icons: + wd["img"] = icons[img] + for keyword, item in self.items.items(): + try: + (wol_idx, wol_key) = keyword.split("__") + value = wol_data["weather"][int(wol_idx)][wol_key] + item(value, trigger=self.get_trigger()) + except: + self.logger.exception( + "Unable to update item %s with value %s" % (item, value) + ) From 776fe9b6dd11aa10aa34ca911360cbed8fa65110 Mon Sep 17 00:00:00 2001 From: Mario Schmidt Date: Tue, 21 Feb 2023 09:21:05 +0100 Subject: [PATCH 7/7] Fix workaround for 'today' --- wetteronline/WetterOnline/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wetteronline/WetterOnline/__init__.py b/wetteronline/WetterOnline/__init__.py index 2a55684..96ac5bb 100644 --- a/wetteronline/WetterOnline/__init__.py +++ b/wetteronline/WetterOnline/__init__.py @@ -60,7 +60,7 @@ def _parse_tree(self, tree): )[-1].strip() # Workaround 'today' becoming 'tomorrow' too early on the site if day == 1 and daystr.lower() != "heute": - continue + break w = { "day": daystr, "date": re.search(