Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugin for wetteronline.de #6

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions wetteronline/WetterOnline/Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]
pylint = "*"

[packages]
requests = "*"
lxml = "*"

[requires]

118 changes: 118 additions & 0 deletions wetteronline/WetterOnline/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import logging
import urllib.parse
import re
import requests

from lxml import html


class WetterOnline:
def __init__(self, location):
self.logger = logging.getLogger(__name__)
self.location = None
self.weather = None
self.url = self.get_url(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"})
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]["geoName"]

if not self.location:
raise Exception("Location not found!")

return "http://www.wetteronline.de/{}/".format(self.location)

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 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":
break
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('"', ""),
}
if day == 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")
return
return weather
102 changes: 102 additions & 0 deletions wetteronline/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
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",
"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 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 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)
)
9 changes: 9 additions & 0 deletions wetteronline/config.md
Original file line number Diff line number Diff line change
@@ -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)

}}

32 changes: 32 additions & 0 deletions wetteronline/item.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{{ select_data = oDict([
('', ''),
('_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)'),
('0__sunhours', 'Sonnenstunden (h)'),
('0__rain_probability', 'Regenwahrscheinlichkeit (%)'),

('_1', '== Morgen =='),
('1__title', 'Wetterlage'),
('1__img', 'Icon'),
('1__src', 'Icon (orig)'),
('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__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)
}}
2 changes: 2 additions & 0 deletions wetteronline/plugin.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[WetterOnline]
class = WetterOnlinePlugin