1
0
Fork 0
nemubot/modules/weather.py

262 lines
10 KiB
Python
Raw Normal View History

2014-04-30 20:41:47 +00:00
# coding=utf-8
"""The weather module. Powered by Dark Sky <https://darksky.net/poweredby/>"""
2014-08-27 23:39:31 +00:00
2014-04-30 20:41:47 +00:00
import datetime
import re
2015-02-11 17:12:39 +00:00
from nemubot import context
from nemubot.exception import IMException
from nemubot.hooks import hook
from nemubot.tools import web
2015-01-05 09:18:40 +00:00
from nemubot.tools.xmlparser.node import ModuleState
from nemubot.module import mapquest
nemubotversion = 4.0
2014-04-30 20:41:47 +00:00
from nemubot.module.more import Response
URL_DSAPI = "https://api.darksky.net/forecast/%s/%%s,%%s?lang=%%s&units=%%s"
2017-09-04 21:54:40 +00:00
UNITS = {
"ca": {
"temperature": "°C",
"distance": "km",
"precipIntensity": "mm/h",
"precip": "cm",
"speed": "km/h",
"pressure": "hPa",
},
"uk2": {
"temperature": "°C",
"distance": "mi",
"precipIntensity": "mm/h",
"precip": "cm",
"speed": "mi/h",
"pressure": "hPa",
},
"us": {
"temperature": "°F",
"distance": "mi",
"precipIntensity": "in/h",
"precip": "in",
"speed": "mi/h",
"pressure": "mbar",
},
"si": {
"temperature": "°C",
"distance": "km",
"precipIntensity": "mm/h",
"precip": "cm",
"speed": "m/s",
"pressure": "hPa",
},
}
2014-04-30 20:41:47 +00:00
def load(context):
if not context.config or "darkskyapikey" not in context.config:
raise ImportError("You need a Dark-Sky API key in order to use this "
"module. Add it to the module configuration file:\n"
"<module name=\"weather\" darkskyapikey=\"XXX\" />\n"
"Register at https://developer.forecast.io/")
2015-02-11 17:12:39 +00:00
context.data.setIndex("name", "city")
global URL_DSAPI
URL_DSAPI = URL_DSAPI % context.config["darkskyapikey"]
2014-04-30 20:41:47 +00:00
2017-09-04 21:54:40 +00:00
def format_wth(wth, flags):
units = UNITS[flags["units"] if "units" in flags and flags["units"] in UNITS else "si"]
return ("{temperature} {units[temperature]} {summary}; precipitation ({precipProbability:.0%} chance) intensity: {precipIntensity} {units[precipIntensity]}; relative humidity: {humidity:.0%}; wind speed: {windSpeed} {units[speed]} {windBearing}°; cloud coverage: {cloudCover:.0%}; pressure: {pressure} {units[pressure]}; ozone: {ozone} DU"
.format(units=units, **wth)
)
2014-04-30 20:41:47 +00:00
2017-09-04 21:54:40 +00:00
def format_forecast_daily(wth, flags):
units = UNITS[flags["units"] if "units" in flags and flags["units"] in UNITS else "si"]
print(units)
return ("{summary}; between {temperatureMin}-{temperatureMax} {units[temperature]}; precipitation ({precipProbability:.0%} chance) intensity: maximum {precipIntensity} {units[precipIntensity]}; relative humidity: {humidity:.0%}; wind speed: {windSpeed} {units[speed]} {windBearing}°; cloud coverage: {cloudCover:.0%}; pressure: {pressure} {units[pressure]}; ozone: {ozone} DU".format(units=units, **wth))
2014-04-30 20:41:47 +00:00
def format_timestamp(timestamp, tzname, tzoffset, format="%c"):
tz = datetime.timezone(datetime.timedelta(hours=tzoffset), tzname)
time = datetime.datetime.fromtimestamp(timestamp, tz=tz)
return time.strftime(format)
2014-04-30 20:41:47 +00:00
def treat_coord(msg):
if len(msg.args) > 0:
2014-04-30 20:41:47 +00:00
# catch dans X[jh]$
if len(msg.args) > 2 and (msg.args[-2] == "dans" or msg.args[-2] == "in" or msg.args[-2] == "next"):
specific = msg.args[-1]
city = " ".join(msg.args[:-2]).lower()
2014-04-30 20:41:47 +00:00
else:
specific = None
city = " ".join(msg.args).lower()
2014-04-30 20:41:47 +00:00
if len(msg.args) == 2:
coords = msg.args
2014-04-30 20:41:47 +00:00
else:
coords = msg.args[0].split(",")
2014-04-30 20:41:47 +00:00
try:
if len(coords) == 2 and str(float(coords[0])) == coords[0] and str(float(coords[1])) == coords[1]:
return coords, specific
except ValueError:
pass
2015-02-11 17:12:39 +00:00
if city in context.data.index:
2014-04-30 20:41:47 +00:00
coords = list()
2015-02-11 17:12:39 +00:00
coords.append(context.data.index[city]["lat"])
coords.append(context.data.index[city]["long"])
return city, coords, specific
else:
geocode = [x for x in mapquest.geocode(city)]
if len(geocode):
coords = list()
coords.append(geocode[0]["latLng"]["lat"])
coords.append(geocode[0]["latLng"]["lng"])
return mapquest.where(geocode[0]), coords, specific
2014-04-30 20:41:47 +00:00
raise IMException("Je ne sais pas où se trouve %s." % city)
2014-04-30 20:41:47 +00:00
else:
raise IMException("indique-moi un nom de ville ou des coordonnées.")
2014-04-30 20:41:47 +00:00
2017-09-04 21:54:40 +00:00
def get_json_weather(coords, lang="en", units="ca"):
wth = web.getJSON(URL_DSAPI % (float(coords[0]), float(coords[1]), lang, units))
2014-04-30 20:41:47 +00:00
# First read flags
2015-06-27 16:14:45 +00:00
if wth is None or "darksky-unavailable" in wth["flags"]:
raise IMException("The given location is supported but a temporary error (such as a radar station being down for maintenace) made data unavailable.")
2014-04-30 20:41:47 +00:00
return wth
2015-11-02 19:19:12 +00:00
@hook.command("coordinates")
def cmd_coordinates(msg):
if len(msg.args) < 1:
raise IMException("indique-moi un nom de ville.")
j = msg.args[0].lower()
2015-02-11 17:12:39 +00:00
if j not in context.data.index:
raise IMException("%s n'est pas une ville connue" % msg.args[0])
2015-02-11 17:12:39 +00:00
coords = context.data.index[j]
return Response("Les coordonnées de %s sont %s,%s" % (msg.args[0], coords["lat"], coords["long"]), channel=msg.channel)
@hook.command("alert",
keywords={
"lang=LANG": "change the output language of weather sumarry; default: en",
2017-09-04 21:54:40 +00:00
"units=UNITS": "return weather conditions in the requested units; default: ca",
})
2014-04-30 20:41:47 +00:00
def cmd_alert(msg):
loc, coords, specific = treat_coord(msg)
wth = get_json_weather(coords,
lang=msg.kwargs["lang"] if "lang" in msg.kwargs else "en",
2017-09-04 21:54:40 +00:00
units=msg.kwargs["units"] if "units" in msg.kwargs else "ca")
2014-04-30 20:41:47 +00:00
res = Response(channel=msg.channel, nomore="No more weather alert", count=" (%d more alerts)")
2014-04-30 20:41:47 +00:00
if "alerts" in wth:
for alert in wth["alerts"]:
if "expires" in alert:
res.append_message("\x03\x02%s\x03\x02 (see %s expire on %s): %s" % (alert["title"], alert["uri"], format_timestamp(int(alert["expires"]), wth["timezone"], wth["offset"]), alert["description"].replace("\n", " ")))
else:
res.append_message("\x03\x02%s\x03\x02 (see %s): %s" % (alert["title"], alert["uri"], alert["description"].replace("\n", " ")))
2014-04-30 20:41:47 +00:00
return res
@hook.command("météo",
help="Display current weather and previsions",
help_usage={
"CITY": "Display the current weather and previsions in CITY",
},
keywords={
"lang=LANG": "change the output language of weather sumarry; default: en",
2017-09-04 21:54:40 +00:00
"units=UNITS": "return weather conditions in the requested units; default: ca",
})
2014-04-30 20:41:47 +00:00
def cmd_weather(msg):
loc, coords, specific = treat_coord(msg)
wth = get_json_weather(coords,
lang=msg.kwargs["lang"] if "lang" in msg.kwargs else "en",
2017-09-04 21:54:40 +00:00
units=msg.kwargs["units"] if "units" in msg.kwargs else "ca")
2014-04-30 20:41:47 +00:00
res = Response(channel=msg.channel, nomore="No more weather information")
2014-04-30 20:41:47 +00:00
if "alerts" in wth:
alert_msgs = list()
for alert in wth["alerts"]:
if "expires" in alert:
alert_msgs.append("\x03\x02%s\x03\x02 expire on %s" % (alert["title"], format_timestamp(int(alert["expires"]), wth["timezone"], wth["offset"])))
else:
alert_msgs.append("\x03\x02%s\x03\x02" % (alert["title"]))
2014-04-30 20:41:47 +00:00
res.append_message("\x03\x16\x03\x02/!\\\x03\x02 Alert%s:\x03\x16 " % ("s" if len(alert_msgs) > 1 else "") + ", ".join(alert_msgs))
if specific is not None:
gr = re.match(r"^([0-9]*)\s*([a-zA-Z])", specific)
if gr is None or gr.group(1) == "":
gr1 = 1
else:
gr1 = int(gr.group(1))
if gr.group(2).lower() == "h" and gr1 < len(wth["hourly"]["data"]):
hour = wth["hourly"]["data"][gr1]
2017-09-04 21:54:40 +00:00
res.append_message("\x03\x02At %sh:\x03\x02 %s" % (format_timestamp(int(hour["time"]), wth["timezone"], wth["offset"], '%H'), format_wth(hour, wth["flags"])))
2014-04-30 20:41:47 +00:00
elif gr.group(2).lower() == "d" and gr1 < len(wth["daily"]["data"]):
day = wth["daily"]["data"][gr1]
2017-09-04 21:54:40 +00:00
res.append_message("\x03\x02On %s:\x03\x02 %s" % (format_timestamp(int(day["time"]), wth["timezone"], wth["offset"], '%A'), format_forecast_daily(day, wth["flags"])))
2014-04-30 20:41:47 +00:00
else:
res.append_message("I don't understand %s or information is not available" % specific)
else:
2017-09-04 21:54:40 +00:00
res.append_message("\x03\x02Currently:\x03\x02 " + format_wth(wth["currently"], wth["flags"]))
2014-04-30 20:41:47 +00:00
nextres = "\x03\x02Today:\x03\x02 %s " % wth["daily"]["data"][0]["summary"]
if "minutely" in wth:
nextres += "\x03\x02Next hour:\x03\x02 %s " % wth["minutely"]["summary"]
nextres += "\x03\x02Next 24 hours:\x03\x02 %s \x03\x02Next week:\x03\x02 %s" % (wth["hourly"]["summary"], wth["daily"]["summary"])
res.append_message(nextres)
for hour in wth["hourly"]["data"][1:4]:
res.append_message("\x03\x02At %sh:\x03\x02 %s" % (format_timestamp(int(hour["time"]), wth["timezone"], wth["offset"], '%H'),
2017-09-04 21:54:40 +00:00
format_wth(hour, wth["flags"])))
2014-04-30 20:41:47 +00:00
for day in wth["daily"]["data"][1:]:
res.append_message("\x03\x02On %s:\x03\x02 %s" % (format_timestamp(int(day["time"]), wth["timezone"], wth["offset"], '%A'),
2017-09-04 21:54:40 +00:00
format_forecast_daily(day, wth["flags"])))
2014-04-30 20:41:47 +00:00
return res
gps_ask = re.compile(r"^\s*(?P<city>.*\w)\s*(?:(?:se|est)\s+(?:trouve|situ[ée]*)\s+[aà])\s*(?P<lat>-?[0-9]+(?:[,.][0-9]+))[^0-9.](?P<long>-?[0-9]+(?:[,.][0-9]+))\s*$", re.IGNORECASE)
2015-11-02 19:19:12 +00:00
@hook.ask()
2014-04-30 20:41:47 +00:00
def parseask(msg):
2017-07-18 04:48:15 +00:00
res = gps_ask.match(msg.message)
2014-04-30 20:41:47 +00:00
if res is not None:
city_name = res.group("city").lower()
gps_lat = res.group("lat").replace(",", ".")
gps_long = res.group("long").replace(",", ".")
2015-02-11 17:12:39 +00:00
if city_name in context.data.index:
context.data.index[city_name]["lat"] = gps_lat
context.data.index[city_name]["long"] = gps_long
2014-04-30 20:41:47 +00:00
else:
ms = ModuleState("city")
ms.setAttribute("name", city_name)
ms.setAttribute("lat", gps_lat)
ms.setAttribute("long", gps_long)
2015-02-11 17:12:39 +00:00
context.data.addChild(ms)
context.save()
return Response("ok, j'ai bien noté les coordonnées de %s" % res.group("city"),
2017-07-18 04:32:48 +00:00
msg.channel, msg.frm)