[WeatherPlugin] Because msn weather-api is dead, I switched from msn to yahoo. Saved...
[enigma2-plugins.git] / weatherplugin / src / MSNWeather.py
1 # -*- coding: utf-8 -*-
2 #
3 # WeatherPlugin E2
4 #
5 # Coded by Dr.Best (c) 2012-2013
6 # Support: www.dreambox-tools.info
7 # E-Mail: dr.best@dreambox-tools.info
8 #
9 # This plugin is open source but it is NOT free software.
10 #
11 # This plugin may only be distributed to and executed on hardware which
12 # is licensed by Dream Multimedia GmbH.
13 # In other words:
14 # It's NOT allowed to distribute any parts of this plugin or its source code in ANY way
15 # to hardware which is NOT licensed by Dream Multimedia GmbH.
16 # It's NOT allowed to execute this plugin and its source code or even parts of it in ANY way
17 # on hardware which is NOT licensed by Dream Multimedia GmbH.
18 #
19 # If you want to use or modify the code or parts of it,
20 # you have to keep MY license and inform me about the modifications by mail.
21 #
22 from twisted.internet import defer
23 from twisted.web.client import getPage, downloadPage
24 from enigma import eEnv
25 from os import path as os_path, mkdir as os_mkdir, remove as os_remove, listdir as os_listdir
26 from Components.config import config
27 from Tools.Directories import resolveFilename, SCOPE_SKIN
28 from urllib import quote as urllib_quote
29 import time
30 import json
31
32 #this is for translation support only...
33 possible_conditions= (  _("Tornado"), _("Tropical Storm"), _("Severe Thunderstorms"), _("Thunderstorms"), _("Mixed Rain and Snow"), _("Mixed Rain and Sleet"), _("Mixed Snow and Sleet"), _("Freezing Drizzle"),
34                         _("Drizzle"), _("Freezing Rain"), _("Showers"), _("Snow Flurries"), _("Light Snow Showers"), _("Blowing Snow"), _("Snow"),
35                         _("Hail"), _("Sleet"), _("Dust"), _("Foggy"), _("Haze"), _("Smoky"), _("Blustery"), _("Windy"),
36                         _("Cold"), _("Cloudy"), _("Mostly Cloudy"), _("Partly Cloudy"), _("Clear"), _("Sunny"), _("Rain"), 
37                         _("Fair"), _("Mixed Rain and Hail"), _("Hot"), _("Isolated Thunderstorms"), _("Scattered Thunderstorms"), _("Scattered Showers"),
38                         _("Heavy Snow"), _("Scattered Snow Showers"), _("Thundershowers"), _("Snow Showers"), _("Isolated Thundershowers"), _("Not Available")
39                         )
40
41 class WeatherIconItem:
42         def __init__(self, url = "", filename = "", index = -1, error = False):
43                 self.url = url
44                 self.filename = filename
45                 self.index = index
46                 self.error = error
47                 
48 class MSNWeatherItem:
49         def __init__(self):
50                 self.temperature = ""
51                 self.skytext = ""
52                 self.humidity = ""
53                 self.winddisplay = ""
54                 self.observationtime = ""
55                 self.observationpoint = ""
56                 self.feelslike = ""
57                 self.skycode = ""
58                 self.date = ""
59                 self.day = ""
60                 self.low = ""
61                 self.high = ""
62                 self.skytextday = ""
63                 self.skycodeday = ""
64                 self.shortday = ""
65                 self.iconFilename = ""
66                 self.code = ""
67                 
68 class MSNWeather:
69
70         ERROR = 0
71         OK = 1
72
73         def __init__(self):
74                 path = "/etc/enigma2/weather_icons/"
75                 extension = self.checkIconExtension(path)
76                 if extension is None:
77                         path = os_path.dirname(resolveFilename(SCOPE_SKIN, config.skin.primary_skin.value)) + "/weather_icons/"
78                         extension = self.checkIconExtension(path)
79                 if extension is None:
80                         path = eEnv.resolve("${libdir}/enigma2/python/Plugins/Extensions/WeatherPlugin/weather_icons/")
81                         extension = ".png"
82                 self.setIconPath(path)
83                 self.setIconExtension(extension)
84                 self.initialize()
85
86         def checkIconExtension(self, path):
87                 filename = None
88                 extension = None
89                 if os_path.exists(path):
90                         try:
91                                 filename = os_listdir(path)[0]
92                         except:
93                                 filename = None
94                 if filename is not None:
95                         try:
96                                 extension = os_path.splitext(filename)[1].lower()
97                         except: pass
98                 return extension
99                 
100         def initialize(self):
101                 self.city = ""
102                 self.degreetype = ""
103                 self.imagerelativeurl = ""
104                 self.url = ""
105                 self.weatherItems = {}
106                 self.callback = None
107                 self.callbackShowIcon = None
108                 self.callbackAllIconsDownloaded = None
109                 
110         def cancel(self):
111                 self.callback = None
112                 self.callbackShowIcon = None
113                 
114         def setIconPath(self, iconpath):
115                 if not os_path.exists(iconpath):
116                         os_mkdir(iconpath)
117                 self.iconpath = iconpath
118                 
119         def setIconExtension(self, iconextension):
120                 self.iconextension = iconextension
121                 
122         def getWeatherData(self, degreetype, locationcode, city, callback, callbackShowIcon, callbackAllIconsDownloaded = None ):
123                 self.initialize()
124                 self.city = city
125                 self.callback = callback
126                 self.callbackShowIcon  = callbackShowIcon
127                 self.callbackAllIconsDownloaded = callbackAllIconsDownloaded
128                 u = "+and+u='c'"
129                 if degreetype == "F":
130                         u = ""
131                 url = "http://query.yahooapis.com/v1/public/yql?q=select+*+from+weather.forecast+where+woeid=%s%s&format=json" % (urllib_quote(locationcode), u)
132                 getPage(url).addCallback(self.jsonCallback).addErrback(self.error)
133                 
134         def getDefaultWeatherData(self, callback = None, callbackAllIconsDownloaded = None):
135                 self.initialize()
136                 weatherPluginEntryCount = config.plugins.WeatherPlugin.entrycount.value
137                 if weatherPluginEntryCount >= 1:
138                         weatherPluginEntry = config.plugins.WeatherPlugin.Entry[0]
139                         self.getWeatherData(weatherPluginEntry.degreetype.value, weatherPluginEntry.weatherlocationcode.value, weatherPluginEntry.city.value, callback, None, callbackAllIconsDownloaded)
140                         return 1
141                 else:
142                         return 0
143                 
144         def error(self, error = None):
145                 errormessage = ""
146                 if error is not None:
147                         errormessage = str(error.getErrorMessage())
148                 if self.callback is not None:
149                         self.callback(self.ERROR, errormessage)
150                         
151         
152         def errorIconDownload(self, error = None, item = None):
153                 item.error = True
154                 if os_path.exists(item.filename): # delete 0 kb file
155                         os_remove(item.filename)
156
157         def finishedIconDownload(self, result, item):
158                 if not item.error:
159                         self.showIcon(item.index,item.filename)
160                 
161         def showIcon(self, index, filename):
162                 if self.callbackShowIcon is not None:
163                                 self.callbackShowIcon(index, filename)
164                                 
165         def finishedAllDownloadFiles(self, result):
166                 if self.callbackAllIconsDownloaded is not None:
167                         self.callbackAllIconsDownloaded()
168         
169         def shortMonthToStringNumber(self, shortMonth):
170                 return {
171                         'Jan' : "1",
172                         'Feb' : "2",
173                         'Mar' : "3",
174                         'Apr' : "4",
175                         'May' : "5",
176                         'Jun' : "6",
177                         'Jul' : "7",
178                         'Aug' : "8",
179                         'Sep' : "9", 
180                         'Oct' : "10",
181                         'Nov' : "11",
182                         'Dec' : "12"
183                 }[shortMonth]
184                 
185         def windDirection(self, deg):
186                 if deg >= 12 and deg <= 34:
187                         wind = _("NNE")
188                 elif deg >= 35 and deg <= 56:
189                         wind = _("NE")
190                 elif deg >= 57 and deg <= 79:
191                         wind = _("ENE")
192                 elif deg >= 80 and deg <= 101:
193                         wind = _("E")
194                 elif deg >= 102 and deg <= 124:
195                         wind = _("ESE")
196                 elif deg >= 125 and deg <= 146:
197                         wind = _("SE")
198                 elif deg >= 147 and deg <= 169:
199                         wind = _("SSE")
200                 elif deg >= 170 and deg <= 191:
201                         wind = _("S")
202                 elif deg >= 192 and deg <= 214:
203                         wind = _("SSW")
204                 elif deg >= 215 and deg <= 236:
205                         wind = _("SW")
206                 elif deg >= 237 and deg <= 259:
207                         wind = _("WSW")
208                 elif deg >= 260 and deg <= 281:
209                         wind = _("W")
210                 elif deg >= 282 and deg <= 304:
211                         wind = _("WNW")
212                 elif deg >= 305 and deg <= 326:
213                         wind = _("NW")
214                 elif deg >= 327 and deg <= 349:
215                         wind = _("NNW")
216                 else:
217                         wind = _("N")
218                 return wind
219                 
220         def feelslike(self, T, V):
221             FeelsLike = T
222             if round( ( V + .0 ) / 1.609344 ) > 4:
223                 FeelsLike = ( 13.12 + ( 0.6215 * T ) - ( 11.37 * V**0.16 ) + ( 0.3965 * T * V**0.16 ) )
224             return str( int(round( FeelsLike )))
225             
226                 
227         def jsonCallback(self, jsonstring):
228                 IconDownloadList = []
229                 response = json.loads(jsonstring)
230                 humidity = ""
231                 temperature = "0"
232                 skytext = ""
233                 winddisplay = ""
234                 speed = ""
235                 windspeed = "0.0"
236                 code = ""
237                 observationtime = ""
238                 data = response["query"]["results"]["channel"]
239                 self.url = data['link'].encode("utf-8", 'ignore')
240                 if 'units' in data:
241                         item = data['units']
242                         self.degreetype = item['temperature'].encode("utf-8", 'ignore')
243                         speed = item['speed'].encode("utf-8", 'ignore')
244                 if 'atmosphere' in data:
245                         item = data['atmosphere']
246                         humidity = item['humidity'].encode("utf-8", 'ignore')
247                 if 'wind' in data:
248                         item = data['wind']
249                         windspeed = item['speed'].encode("utf-8", 'ignore')
250                         winddisplay = "%s %s %s" % (windspeed, speed, self.windDirection(int(item['direction'].encode("utf-8", 'ignore'))))
251                 if 'item' in data:
252                         if 'condition' in data['item']:
253                                 item = data['item']['condition']
254                                 temperature = item['temp'].encode("utf-8", 'ignore')
255                                 skytext = _(item['text'].encode("utf-8", 'ignore'))
256                                 code = item['code'].encode("utf-8", 'ignore')
257                                 t = item['date'].encode("utf-8", 'ignore')
258                                 observationtime = t[17:]
259                                 # Wed, 08 Mar 2017 10:00 PM CET
260                                 #da = t[5:16]
261                                 #d = da.replace(da[3:6], self.shortMonthToStringNumber(da[3:6]))
262                                 #c = time.strptime(d,"%d %m %Y")
263                                 #observationtime = "%s, %s %s" % (_(t[0:3]), time.strftime("%d. %b",c), t[17:])
264                                 currentWeather = MSNWeatherItem()
265                                 currentWeather.observationpoint  = "" # no chance to get this info with yahooapi
266                                 currentWeather.humidity = humidity
267                                 currentWeather.temperature = temperature
268                                 currentWeather.winddisplay = winddisplay
269                                 if self.degreetype != "F":
270                                         currentWeather.feelslike = self.feelslike(int(temperature), int(round(float(windspeed) + 0.5)))
271                                 currentWeather.skytext = skytext
272                                 currentWeather.observationtime = observationtime
273                                 currentWeather.skycode = "%s%s" % (code, self.iconextension)
274                                 currentWeather.code = code
275                                 filename = "%s%s"  % (self.iconpath, currentWeather.skycode)
276                                 currentWeather.iconFilename = filename
277                                 #if not os_path.exists(filename):
278                                 #       url = "http://l.yimg.com/a/i/us/we/52/%s.gif" % (currentWeather.skycode)
279                                 #       IconDownloadList.append(WeatherIconItem(url = url,filename = filename, index = -1))
280                                 #else:
281                                 self.showIcon(-1,filename)
282                                 self.weatherItems[str(-1)] = currentWeather
283                         if 'forecast' in data['item']:
284                                 forecast = data['item']['forecast']
285                                 for count, item in enumerate(forecast):
286                                         if count >= 5:
287                                                 break
288                                         weather = MSNWeatherItem()
289                                         date = item['date'].encode("utf-8", 'ignore')
290                                         weather.date = date.replace(date[3:6], self.shortMonthToStringNumber(date[3:6]))
291                                         c = time.strptime(weather.date,"%d %m %Y")
292                                         weather.day = time.strftime("%A",c) #_(item['day'].encode("utf-8", 'ignore'))
293                                         weather.shortday = _(item['day'].encode("utf-8", 'ignore'))
294                                         weather.low = item['low'].encode("utf-8", 'ignore')
295                                         weather.high = item['high'].encode("utf-8", 'ignore')
296                                         weather.skytextday = _(item['text'].encode("utf-8", 'ignore'))
297                                         weather.skycodeday = "%s%s" % (item['code'].encode("utf-8", 'ignore'), self.iconextension)
298                                         weather.code = item['code'].encode("utf-8", 'ignore')
299                                         filename = "%s%s"  % (self.iconpath, weather.skycodeday)
300                                         weather.iconFilename = filename
301                                         #if not os_path.exists(filename):
302                                         #       url = "http://l.yimg.com/a/i/us/we/52/%s.gif" % (weather.skycodeday)
303                                         #       IconDownloadList.append(WeatherIconItem(url = url,filename = filename, index = count+1))
304                                         #else:
305                                         self.showIcon(count+1,filename)
306                                         self.weatherItems[str(count+1)] = weather
307                 
308                 if len(IconDownloadList) != 0:
309                         ds = defer.DeferredSemaphore(tokens=len(IconDownloadList))
310                         downloads = [ds.run(download,item ).addErrback(self.errorIconDownload, item).addCallback(self.finishedIconDownload,item) for item in IconDownloadList]
311                         finished = defer.DeferredList(downloads).addErrback(self.error).addCallback(self.finishedAllDownloadFiles)
312                 else:
313                         self.finishedAllDownloadFiles(None)
314                         
315                 if self.callback is not None:
316                         self.callback(self.OK, None)
317                 
318 def download(item):
319         return downloadPage(item.url, file(item.filename, 'wb'))