236 lines
8.0 KiB
Python
Raw Normal View History

2024-03-10 20:37:11 -04:00
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
import json
from powerline.lib.url import urllib_read, urllib_urlencode
from powerline.lib.threaded import KwThreadedSegment
from powerline.segments import with_docstring
# XXX Warning: module name must not be equal to the segment name as long as this
# segment is imported into powerline.segments.common module.
# Weather condition code descriptions available at
# http://developer.yahoo.com/weather/#codes
weather_conditions_codes = (
('tornado', 'stormy'), # 0
('tropical_storm', 'stormy'), # 1
('hurricane', 'stormy'), # 2
('severe_thunderstorms', 'stormy'), # 3
('thunderstorms', 'stormy'), # 4
('mixed_rain_and_snow', 'rainy' ), # 5
('mixed_rain_and_sleet', 'rainy' ), # 6
('mixed_snow_and_sleet', 'snowy' ), # 7
('freezing_drizzle', 'rainy' ), # 8
('drizzle', 'rainy' ), # 9
('freezing_rain', 'rainy' ), # 10
('showers', 'rainy' ), # 11
('showers', 'rainy' ), # 12
('snow_flurries', 'snowy' ), # 13
('light_snow_showers', 'snowy' ), # 14
('blowing_snow', 'snowy' ), # 15
('snow', 'snowy' ), # 16
('hail', 'snowy' ), # 17
('sleet', 'snowy' ), # 18
('dust', 'foggy' ), # 19
('fog', 'foggy' ), # 20
('haze', 'foggy' ), # 21
('smoky', 'foggy' ), # 22
('blustery', 'windy' ), # 23
('windy', ), # 24
('cold', 'day' ), # 25
('clouds', 'cloudy'), # 26
('mostly_cloudy_night', 'cloudy'), # 27
('mostly_cloudy_day', 'cloudy'), # 28
('partly_cloudy_night', 'cloudy'), # 29
('partly_cloudy_day', 'cloudy'), # 30
('clear_night', 'night' ), # 31
('sun', 'sunny' ), # 32
('fair_night', 'night' ), # 33
('fair_day', 'day' ), # 34
('mixed_rain_and_hail', 'rainy' ), # 35
('hot', 'sunny' ), # 36
('isolated_thunderstorms', 'stormy'), # 37
('scattered_thunderstorms', 'stormy'), # 38
('scattered_thunderstorms', 'stormy'), # 39
('scattered_showers', 'rainy' ), # 40
('heavy_snow', 'snowy' ), # 41
('scattered_snow_showers', 'snowy' ), # 42
('heavy_snow', 'snowy' ), # 43
('partly_cloudy', 'cloudy'), # 44
('thundershowers', 'rainy' ), # 45
('snow_showers', 'snowy' ), # 46
('isolated_thundershowers', 'rainy' ), # 47
)
# ('day', (25, 34)),
# ('rainy', (5, 6, 8, 9, 10, 11, 12, 35, 40, 45, 47)),
# ('cloudy', (26, 27, 28, 29, 30, 44)),
# ('snowy', (7, 13, 14, 15, 16, 17, 18, 41, 42, 43, 46)),
# ('stormy', (0, 1, 2, 3, 4, 37, 38, 39)),
# ('foggy', (19, 20, 21, 22, 23)),
# ('sunny', (32, 36)),
# ('night', (31, 33))):
weather_conditions_icons = {
'day': 'DAY',
'blustery': 'WIND',
'rainy': 'RAIN',
'cloudy': 'CLOUDS',
'snowy': 'SNOW',
'stormy': 'STORM',
'foggy': 'FOG',
'sunny': 'SUN',
'night': 'NIGHT',
'windy': 'WINDY',
'not_available': 'NA',
'unknown': 'UKN',
}
temp_conversions = {
'C': lambda temp: temp,
'F': lambda temp: (temp * 9 / 5) + 32,
'K': lambda temp: temp + 273.15,
}
# Note: there are also unicode characters for units: ℃, ℉ and
temp_units = {
'C': '°C',
'F': '°F',
'K': 'K',
}
class WeatherSegment(KwThreadedSegment):
interval = 600
default_location = None
location_urls = {}
@staticmethod
def key(location_query=None, **kwargs):
return location_query
def get_request_url(self, location_query):
try:
return self.location_urls[location_query]
except KeyError:
if location_query is None:
location_data = json.loads(urllib_read('http://geoip.nekudo.com/api/'))
location = ','.join((
location_data['city'],
location_data['country']['name'],
location_data['country']['code']
))
self.info('Location returned by nekudo is {0}', location)
else:
location = location_query
query_data = {
'q':
'use "https://raw.githubusercontent.com/yql/yql-tables/master/weather/weather.bylocation.xml" as we;'
'select * from weather.forecast where woeid in'
' (select woeid from geo.places(1) where text="{0}") and u="c"'.format(location).encode('utf-8'),
'format': 'json',
}
self.location_urls[location_query] = url = (
'http://query.yahooapis.com/v1/public/yql?' + urllib_urlencode(query_data))
return url
def compute_state(self, location_query):
url = self.get_request_url(location_query)
raw_response = urllib_read(url)
if not raw_response:
self.error('Failed to get response')
return None
response = json.loads(raw_response)
try:
condition = response['query']['results']['channel']['item']['condition']
condition_code = int(condition['code'])
temp = float(condition['temp'])
except (KeyError, ValueError):
self.exception('Yahoo returned malformed or unexpected response: {0}', repr(raw_response))
return None
try:
icon_names = weather_conditions_codes[condition_code]
except IndexError:
if condition_code == 3200:
icon_names = ('not_available',)
self.warn('Weather is not available for location {0}', self.location)
else:
icon_names = ('unknown',)
self.error('Unknown condition code: {0}', condition_code)
return (temp, icon_names)
def render_one(self, weather, icons=None, unit='C', temp_format=None, temp_coldest=-30, temp_hottest=40, **kwargs):
if not weather:
return None
temp, icon_names = weather
for icon_name in icon_names:
if icons:
if icon_name in icons:
icon = icons[icon_name]
break
else:
icon = weather_conditions_icons[icon_names[-1]]
temp_format = temp_format or ('{temp:.0f}' + temp_units[unit])
converted_temp = temp_conversions[unit](temp)
if temp <= temp_coldest:
gradient_level = 0
elif temp >= temp_hottest:
gradient_level = 100
else:
gradient_level = (temp - temp_coldest) * 100.0 / (temp_hottest - temp_coldest)
groups = ['weather_condition_' + icon_name for icon_name in icon_names] + ['weather_conditions', 'weather']
return [
{
'contents': icon + ' ',
'highlight_groups': groups,
'divider_highlight_group': 'background:divider',
},
{
'contents': temp_format.format(temp=converted_temp),
'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'],
'divider_highlight_group': 'background:divider',
'gradient_level': gradient_level,
},
]
weather = with_docstring(WeatherSegment(),
'''Return weather from Yahoo! Weather.
Uses GeoIP lookup from http://geoip.nekudo.com to automatically determine
your current location. This should be changed if youre in a VPN or if your
IP address is registered at another location.
Returns a list of colorized icon and temperature segments depending on
weather conditions.
:param str unit:
temperature unit, can be one of ``F``, ``C`` or ``K``
:param str location_query:
location query for your current location, e.g. ``oslo, norway``
:param dict icons:
dict for overriding default icons, e.g. ``{'heavy_snow' : u''}``
:param str temp_format:
format string, receives ``temp`` as an argument. Should also hold unit.
:param float temp_coldest:
coldest temperature. Any temperature below it will have gradient level equal
to zero.
:param float temp_hottest:
hottest temperature. Any temperature above it will have gradient level equal
to 100. Temperatures between ``temp_coldest`` and ``temp_hottest`` receive
gradient level that indicates relative position in this interval
(``100 * (cur-coldest) / (hottest-coldest)``).
Divider highlight group used: ``background:divider``.
Highlight groups used: ``weather_conditions`` or ``weather``, ``weather_temp_gradient`` (gradient) or ``weather``.
Also uses ``weather_conditions_{condition}`` for all weather conditions supported by Yahoo.
''')