diff --git a/modules/ilmatieteenlaitos/__init__.py b/modules/ilmatieteenlaitos/__init__.py new file mode 100644 index 00000000..427fe8e6 --- /dev/null +++ b/modules/ilmatieteenlaitos/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2015 Matthieu Weber +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +from .module import IlmatieteenlaitosModule + +__all__ = ['IlmatieteenlaitosModule'] diff --git a/modules/ilmatieteenlaitos/browser.py b/modules/ilmatieteenlaitos/browser.py new file mode 100644 index 00000000..55446235 --- /dev/null +++ b/modules/ilmatieteenlaitos/browser.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2015 Matthieu Weber +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + +from weboob.browser.browsers import PagesBrowser +from weboob.browser.url import URL +from .pages import WeatherPage, SearchCitiesPage + +__all__ = ['IlmatieteenlaitosBrowser'] + + +class IlmatieteenlaitosBrowser(PagesBrowser): + BASEURL = 'http://ilmatieteenlaitos.fi' + cities = URL('/etusivu\?p_p_id=locationmenuportlet_WAR_fmiwwwweatherportlets&p_p_lifecycle=2&p_p_state=normal&' + 'p_p_mode=view&p_p_cacheability=cacheLevelFull&term=(?P.*)', SearchCitiesPage) + weather_query = URL('/paikallissaa\?p_p_id=locationmenuportlet_WAR_fmiwwwweatherportlets&p_p_lifecycle=1&' + 'p_p_state=normal&p_p_mode=view&_locationmenuportlet_WAR_fmiwwwweatherportlets_action=' + 'changelocation') + weather = URL('/saa/(?P.*)', WeatherPage) + + def iter_city_search(self, pattern): + return self.cities.go(pattern=pattern).iter_cities() + + def iter_forecast(self, city): + return self.weather_query.go(data={"place": city.name, "forecast": "short"}).iter_forecast() + + def get_current(self, city): + return self.weather_query.go(data={"place": city.name, "forecast": "short"}).get_current() diff --git a/modules/ilmatieteenlaitos/favicon.png b/modules/ilmatieteenlaitos/favicon.png new file mode 100644 index 00000000..12657872 Binary files /dev/null and b/modules/ilmatieteenlaitos/favicon.png differ diff --git a/modules/ilmatieteenlaitos/module.py b/modules/ilmatieteenlaitos/module.py new file mode 100644 index 00000000..9edb9224 --- /dev/null +++ b/modules/ilmatieteenlaitos/module.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2015 Matthieu Weber +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +from weboob.capabilities.weather import CapWeather, CityNotFound +from weboob.tools.backend import Module +from weboob.capabilities.base import find_object +from .browser import IlmatieteenlaitosBrowser + + +__all__ = ['IlmatieteenlaitosModule'] + + +class IlmatieteenlaitosModule(Module, CapWeather): + NAME = 'ilmatieteenlaitos' + MAINTAINER = u'Matthieu Weber' + EMAIL = 'mweber+weboob@free.fr' + VERSION = '1.1' + DESCRIPTION = 'Get forecasts from the Ilmatieteenlaitos.fi website' + LICENSE = 'AGPLv3+' + BROWSER = IlmatieteenlaitosBrowser + + def get_current(self, city_id): + return self.browser.get_current(self.get_city(city_id)) + + def iter_forecast(self, city_id): + return self.browser.iter_forecast(self.get_city(city_id)) + + def iter_city_search(self, pattern): + return self.browser.iter_city_search(pattern) + + def get_city(self, _id): + return find_object(self.iter_city_search(_id), id=_id, error=CityNotFound) diff --git a/modules/ilmatieteenlaitos/pages.py b/modules/ilmatieteenlaitos/pages.py new file mode 100644 index 00000000..bec4ff58 --- /dev/null +++ b/modules/ilmatieteenlaitos/pages.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2015 Matthieu Weber +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + +from datetime import date +from itertools import imap, ifilter + +from weboob.browser.pages import JsonPage, HTMLPage +from weboob.browser.elements import ItemElement, ListElement, method +from weboob.capabilities.weather import Forecast, Current, City, Temperature +from weboob.browser.filters.json import Dict +from weboob.browser.filters.standard import Filter, CleanText, CleanDecimal, Regexp, Format, Date + + +class DictElement(ListElement): + def find_elements(self): + if self.item_xpath is not None: + for el in self.el: + yield el + else: + yield self.el + + +class Id(Filter): + def filter(self, txt): + return txt.split(", ")[0] + + +class SearchCitiesPage(JsonPage): + @method + class iter_cities(DictElement): + item_xpath = '.' + ignore_duplicate = True + + class item(ItemElement): + klass = City + + obj_id = Id(Dict('id')) + obj_name = Dict('value') + + +class WeatherPage(HTMLPage): + @method + class iter_forecast(ListElement): + item_xpath = ('//div[contains(@class, "mid") and contains(@class, "local-weather-forecast")]//' + 'tr[@class="meteogram-dates"]/td') + + class item(ItemElement): + klass = Forecast + + obj_id = CleanText('.//span/@title') + + def obj_date(self): + months = [u'tammikuuta', u'helmikuuta', u'maaliskuuta', u'huhtikuuta', u'toukokuuta', u'kesäkuuta', + u'heinäkuuta', u'elokuuta', u'syyskuuta', u'lokakuuta', u'marraskuuta', u'joulukuuta'] + d = CleanText('.//span/@title')(self).split() + return date(int(d[2]), months.index(d[1])+1, int(d[0].strip("."))) + + def temperatures(self): + offset = int(CleanText('string(sum(./preceding-sibling::td/@colspan))')(self)) + length = int(CleanText('@colspan')(self)) + temps = CleanText('../../../tbody/tr[@class="meteogram-temperatures"]/td[position() > %d ' + 'and position() <= %d]/span' % (offset, offset+length))(self) + return [float(_.strip(u'\xb0')) for _ in temps.split()] + + def obj_low(self): + return Temperature(min(self.temperatures()), u'C') + + def obj_high(self): + return Temperature(max(self.temperatures()), u'C') + + def obj_text(self): + offset = int(CleanText('string(sum(./preceding-sibling::td/@colspan))')(self)) + length = int(CleanText('@colspan')(self)) + hour_test = ('../../tr[@class="meteogram-times"]/td[position() > %d and position() <= %d ' + 'and .//text() = "%%s"]' % (offset, offset+length)) + hour_offset = 'string(count(%s/preceding-sibling::td)+1)' % (hour_test) + values = [ + '../../../tbody/tr[@class="meteogram-weather-symbols"]/td[position() = %d]/div/@title', + '../../../tbody/tr[@class="meteogram-apparent-temperatures"]/td[position() = %d]/div/@title', + '../../../tbody/tr[@class="meteogram-wind-symbols"]/td[position() = %d]/div/@title', + '../../../tbody/tr[@class="meteogram-probabilities-of-precipitation"]/td[position() = %d]' + + '/div/@title', + '../../../tbody/tr[@class="meteogram-hourly-precipitation-values"]/td[position() = %d]/span/@title', + ] + + def descriptive_text_for_hour(hour): + hour_exists = CleanText(hour_test % hour)(self) == hour + if hour_exists: + offset = int(CleanText(hour_offset % hour)(self)) + + def info_for_value(value): + return CleanText(value % offset)(self).replace(u'edeltävän tunnin ', u'') + return ("klo %s: " % hour) + ", ".join(ifilter(bool, imap(info_for_value, values))) + + return u'\n' + u'\n'.join(ifilter(bool, imap(descriptive_text_for_hour, ["02", "14"]))) + + @method + class get_current(ItemElement): + klass = Current + + obj_id = date.today() + obj_date = Date(Regexp(CleanText('//table[@class="observation-text"]//span[@class="time-stamp"]'), + r'^(\d+\.\d+.\d+)')) + obj_text = Format(u'%s, %s, %s', + CleanText(u'(//table[@class="observation-text"])//tr[2]/td[2]'), + CleanText(u'(//table[@class="observation-text"])//tr[5]/td[1]'), + CleanText(u'(//table[@class="observation-text"])//tr[4]/td[2]')) + + def obj_temp(self): + path = u'//table[@class="observation-text"]//span[@class="parameter-name" and text() = "Lämpötila"]' + \ + u'/../span[@class="parameter-value"]' + temp = CleanDecimal(Regexp(CleanText(path), r'^([^ \xa0]+)'), replace_dots=True)(self) + unit = Regexp(CleanText(path), r'\xb0(\w)')(self) + return Temperature(float(temp), unit) diff --git a/modules/ilmatieteenlaitos/test.py b/modules/ilmatieteenlaitos/test.py new file mode 100644 index 00000000..1fc10252 --- /dev/null +++ b/modules/ilmatieteenlaitos/test.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2015 Matthieu Weber +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +from weboob.tools.test import BackendTest + + +class IlmatieteenlaitosTest(BackendTest): + MODULE = 'ilmatieteenlaitos' + + def test_ilmatieteenlaitos(self): + l = list(self.backend.iter_city_search('helsinki')) + self.assertTrue(len(l) > 0) + + city = l[0] + current = self.backend.get_current(city.id) + self.assertTrue(current.temp.value > -50 and current.temp.value < 50) + + forecasts = list(self.backend.iter_forecast(city.id)) + self.assertTrue(len(forecasts) > 0)