· 6 years ago · Apr 13, 2020, 12:08 PM
1# coding=utf-8
2# Copyright 2008, Sean B. Palmer, inamidst.com
3# Copyright 2012, Elsie Powell, embolalia.com
4# Copyright 2018, Rusty Bower, rustybower.com
5# Licensed under the Eiffel Forum License 2.
6from __future__ import unicode_literals, absolute_import, print_function, division
7
8from sopel.config.types import StaticSection, ValidatedAttribute
9from sopel.module import commands, example, NOLIMIT
10from sopel.modules.units import c_to_f
11
12from datetime import datetime
13
14import requests
15
16
17# Define our sopel weather configuration
18class WeatherSection(StaticSection):
19 geocoords_provider = ValidatedAttribute('geocoords_provider', str, default='LocationIQ')
20 geocoords_api_key = ValidatedAttribute('geocoords_api_key', str, default='')
21 weather_provider = ValidatedAttribute('weather_provider', str, default='DarkSky')
22 weather_api_key = ValidatedAttribute('weather_api_key', str, default='3fe9434')
23
24
25def setup(bot):
26 bot.config.define_section('weather', WeatherSection)
27
28
29# Walk the user through defining variables required
30def configure(config):
31 config.define_section('weather', WeatherSection, validate=False)
32 config.weather.configure_setting(
33 'geocoords_provider',
34 'Enter GeoCoords API Provider:'
35 )
36 config.weather.configure_setting(
37 'geocoords_api_key',
38 'Enter GeoCoords API Key:'
39 )
40 config.weather.configure_setting(
41 'weather_provider',
42 'Enter Weather API Provider:'
43 )
44 config.weather.configure_setting(
45 'weather_api_key',
46 'Enter Weather API Key:'
47 )
48
49
50def get_temp(temp):
51 try:
52 temp = float(temp)
53 except (KeyError, TypeError, ValueError):
54 return 'unknown'
55 return u'%d\u00B0C (%d\u00B0F)' % (round(temp), round(c_to_f(temp)))
56
57
58def get_humidity(humidity):
59 try:
60 humidity = int(humidity * 100)
61 except (KeyError, TypeError, ValueError):
62 return 'unknown'
63 return "Humidity: %s%%" % humidity
64
65
66def get_wind(speed, bearing):
67 m_s = float(round(speed, 1))
68 speed = int(round(m_s * 1.94384, 0))
69 bearing = int(bearing)
70
71 if speed < 1:
72 description = 'Calm'
73 elif speed < 4:
74 description = 'Light air'
75 elif speed < 7:
76 description = 'Light breeze'
77 elif speed < 11:
78 description = 'Gentle breeze'
79 elif speed < 16:
80 description = 'Moderate breeze'
81 elif speed < 22:
82 description = 'Fresh breeze'
83 elif speed < 28:
84 description = 'Strong breeze'
85 elif speed < 34:
86 description = 'Near gale'
87 elif speed < 41:
88 description = 'Gale'
89 elif speed < 48:
90 description = 'Strong gale'
91 elif speed < 56:
92 description = 'Storm'
93 elif speed < 64:
94 description = 'Violent storm'
95 else:
96 description = 'Hurricane'
97
98 if (bearing <= 22.5) or (bearing > 337.5):
99 bearing = u'\u2193'
100 elif (bearing > 22.5) and (bearing <= 67.5):
101 bearing = u'\u2199'
102 elif (bearing > 67.5) and (bearing <= 112.5):
103 bearing = u'\u2190'
104 elif (bearing > 112.5) and (bearing <= 157.5):
105 bearing = u'\u2196'
106 elif (bearing > 157.5) and (bearing <= 202.5):
107 bearing = u'\u2191'
108 elif (bearing > 202.5) and (bearing <= 247.5):
109 bearing = u'\u2197'
110 elif (bearing > 247.5) and (bearing <= 292.5):
111 bearing = u'\u2192'
112 elif (bearing > 292.5) and (bearing <= 337.5):
113 bearing = u'\u2198'
114
115 return description + ' ' + str(m_s) + 'm/s (' + bearing + ')'
116
117
118def get_geocoords(bot, trigger):
119 url = "https://us1.locationiq.com/v1/search.php" # This can be updated to their EU endpoint for EU users
120 data = {
121 'key': bot.config.weather.geocoords_api_key,
122 'q': trigger.group(2),
123 'format': 'json',
124 'addressdetails': 1,
125 'limit': 1
126 }
127 r = requests.get(url, params=data)
128 if r.status_code != 200:
129 raise Exception(r.json()['error'])
130
131 latitude = r.json()[0]['lat']
132 longitude = r.json()[0]['lon']
133 address = r.json()[0]['address']
134
135 # Zip codes give us town versus city
136 if 'city' in address.keys():
137 location = '{}, {}, {}'.format(address['city'],
138 address['state'],
139 address['country_code'].upper())
140 elif 'town' in address.keys():
141 location = '{}, {}, {}'.format(address['town'],
142 address['state'],
143 address['country_code'].upper())
144 elif 'county' in address.keys():
145 location = '{}, {}, {}'.format(address['county'],
146 address['state'],
147 address['country_code'].upper())
148 elif 'city_district' in address.keys():
149 location = '{}, {}'.format(address['city_district'],
150 address['country_code'].upper())
151 else:
152 location = 'Unknown'
153
154 return latitude, longitude, location
155
156
157# 24h Forecast: Oshkosh, US: Broken Clouds, High: 0°C (32°F), Low: -7°C (19°F)
158def get_forecast(bot, trigger):
159 location = trigger.group(2)
160 if not location:
161 latitude = bot.db.get_nick_value(trigger.nick, 'latitude')
162 longitude = bot.db.get_nick_value(trigger.nick, 'longitude')
163 location = bot.db.get_nick_value(trigger.nick, 'location')
164 if not latitude and not longitude:
165 return "I don't know where you live. " \
166 "Give me a location, like {pfx}{command} London, " \
167 "or tell me where you live by saying {pfx}setlocation " \
168 "London, for example.".format(command=trigger.group(1),
169 pfx=bot.config.core.help_prefix)
170 else:
171 latitude, longitude, location = get_geocoords(bot, trigger)
172
173 # Query DarkSky
174 url = 'https://api.darksky.net/forecast/{}/{},{}'.format(
175 bot.config.weather.weather_api_key,
176 latitude,
177 longitude
178 )
179 data = {
180 'exclude': 'currently,minutely,hourly,alerts,flags', # Exclude extra data we don't want/need
181 'units': 'si'
182 }
183 r = requests.get(url, params=data)
184 data = r.json()
185 if r.status_code != 200:
186 raise Exception(data['error'])
187 else:
188 forecast = ''
189 forecast += '{location}'.format(location=location)
190 for day in data['daily']['data'][0:4]:
191 dow = datetime.fromtimestamp(day['time']).strftime('%A')
192 summary = day['summary'].strip('.')
193 high_temp = get_temp(day['temperatureHigh'])
194 low_temp = get_temp(day['temperatureLow'])
195 forecast += ' :: {dow} - {summary} - {high_temp} / {low_temp}'.format(
196 dow=dow,
197 summary=summary,
198 high_temp=high_temp,
199 low_temp=low_temp
200 )
201 return forecast
202
203
204def get_weather(bot, trigger):
205 location = trigger.group(2)
206 if not location:
207 latitude = bot.db.get_nick_value(trigger.nick, 'latitude')
208 longitude = bot.db.get_nick_value(trigger.nick, 'longitude')
209 location = bot.db.get_nick_value(trigger.nick, 'location')
210 if not latitude and not longitude:
211 return "I don't know where you live. " \
212 "Give me a location, like {pfx}{command} London, " \
213 "or tell me where you live by saying {pfx}setlocation " \
214 "London, for example.".format(command=trigger.group(1),
215 pfx=bot.config.core.help_prefix)
216 else:
217 latitude, longitude, location = get_geocoords(bot, trigger)
218
219 # Query DarkSky
220 url = 'https://api.darksky.net/forecast/{}/{},{}'.format(
221 bot.config.weather.weather_api_key,
222 latitude,
223 longitude
224 )
225 data = {
226 'exclude': 'minutely,hourly,daily,alerts,flags', # Exclude extra data we don't want/need
227 'units': 'si'
228 }
229 r = requests.get(url, params=data)
230 data = r.json()
231 if r.status_code != 200:
232 return 'Error: {}'.format(data['error'])
233 else:
234 temp = get_temp(data['currently']['temperature'])
235 condition = data['currently']['summary']
236 humidity = get_humidity(data['currently']['humidity'])
237 wind = get_wind(data['currently']['windSpeed'], data['currently']['windBearing'])
238 uvindex = data['currently']['uvIndex']
239 return u'%s: %s, %s, %s, UV Index: %s, %s' % (location, temp, condition, humidity, uvindex, wind)
240
241
242@commands('weather', 'wea')
243@example('.weather')
244@example('.weather London')
245@example('.weather Seattle, US')
246@example('.weather 90210')
247def weather_command(bot, trigger):
248 """.weather location - Show the weather at the given location."""
249 if bot.config.weather.weather_api_key is None or bot.config.weather.weather_api_key == '':
250 return bot.reply("Weather API key missing. Please configure this module.")
251 if bot.config.weather.geocoords_api_key is None or bot.config.weather.geocoords_api_key == '':
252 return bot.reply("GeoCoords API key missing. Please configure this module.")
253 return bot.say(get_weather(bot, trigger))
254
255
256@commands('forecast')
257@example('.forecast')
258@example('.forecast London')
259@example('.forecast Seattle, US')
260@example('.forecast 90210')
261def forecast_command(bot, trigger):
262 """.forecast location - Show the weather forecast for tomorrow at the given location."""
263 if bot.config.weather.weather_api_key is None or bot.config.weather.weather_api_key == '':
264 return bot.reply("Weather API key missing. Please configure this module.")
265 if bot.config.weather.geocoords_api_key is None or bot.config.weather.geocoords_api_key == '':
266 return bot.reply("GeoCoords API key missing. Please configure this module.")
267 return bot.say(get_forecast(bot, trigger))
268
269
270@commands('setlocation')
271@example('.setlocation London')
272@example('.setlocation Seattle, US')
273@example('.setlocation 90210')
274@example('.setlocation w7174408')
275def update_location(bot, trigger):
276 if bot.config.weather.geocoords_api_key is None or bot.config.weather.geocoords_api_key == '':
277 return bot.reply("GeoCoords API key missing. Please configure this module.")
278
279 # Return an error if no location is provided
280 if not trigger.group(2):
281 bot.reply('Give me a location, like "London" or "90210".')
282 return NOLIMIT
283
284 # Get GeoCoords
285 latitude, longitude, location = get_geocoords(bot, trigger)
286
287 # Assign Latitude & Longitude to user
288 bot.db.set_nick_value(trigger.nick, 'latitude', latitude)
289 bot.db.set_nick_value(trigger.nick, 'longitude', longitude)
290 bot.db.set_nick_value(trigger.nick, 'location', location)
291
292 return bot.reply('I now have you at {}'.format(location))