2014-04-30 20:41:47 +00:00
# coding=utf-8
2016-11-10 17:32:50 +00:00
""" 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
2015-10-30 20:57:45 +00:00
from nemubot . exception import IMException
2015-01-03 19:34:44 +00:00
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
2014-08-28 16:05:21 +00:00
2014-08-15 23:26:45 +00:00
import mapquest
2015-06-03 20:07:06 +00:00
nemubotversion = 4.0
2014-04-30 20:41:47 +00:00
2014-09-26 16:00:22 +00:00
from more import Response
2016-11-10 17:32:50 +00:00
URL_DSAPI = " https://api.darksky.net/forecast/ %s / %% s, %% s?lang= %% s&units= %% s "
2014-12-17 06:32:34 +00:00
2014-04-30 20:41:47 +00:00
def load ( context ) :
2015-10-27 17:03:28 +00:00
if not context . config or " darkskyapikey " not in context . config :
2015-06-03 20:07:06 +00:00
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 http://developer.forecast.io/ " )
2015-02-11 17:12:39 +00:00
context . data . setIndex ( " name " , " city " )
2015-06-03 20:07:06 +00:00
global URL_DSAPI
URL_DSAPI = URL_DSAPI % context . config [ " darkskyapikey " ]
2014-04-30 20:41:47 +00:00
def format_wth ( wth ) :
2016-11-10 17:32:50 +00:00
return ( " {temperature} °C {summary} ; precipitation ( {precipProbability:.0%} chance) intensity: {precipIntensity} mm/h; relative humidity: {humidity:.0%} ; wind speed: {windSpeed} km/s {windBearing} °; cloud coverage: {cloudCover:.0%} ; pressure: {pressure} hPa; ozone: {ozone} DU "
. format ( * * wth )
)
2014-04-30 20:41:47 +00:00
2014-12-17 06:32:34 +00:00
2014-04-30 20:41:47 +00:00
def format_forecast_daily ( wth ) :
2016-11-10 17:32:50 +00:00
return ( " {summary} ; between {temperatureMin} - {temperatureMax} °C; precipitation ( {precipProbability:.0%} chance) intensity: maximum {precipIntensity} mm/h; relative humidity: {humidity:.0%} ; wind speed: {windSpeed} km/h {windBearing} °; cloud coverage: {cloudCover:.0%} ; pressure: {pressure} hPa; ozone: {ozone} DU " . format ( * * wth ) )
2014-04-30 20:41:47 +00:00
2014-12-17 06:32:34 +00:00
2014-05-05 08:40:07 +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 ) :
2014-10-21 18:55:39 +00:00
if len ( msg . args ) > 0 :
2014-04-30 20:41:47 +00:00
# catch dans X[jh]$
2014-10-21 18:55:39 +00:00
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
2014-10-21 18:55:39 +00:00
city = " " . join ( msg . args ) . lower ( )
2014-04-30 20:41:47 +00:00
2014-10-21 18:55:39 +00:00
if len ( msg . args ) == 2 :
coords = msg . args
2014-04-30 20:41:47 +00:00
else :
2014-10-21 18:55:39 +00:00
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 " ] )
2014-10-21 18:55:39 +00:00
return city , coords , specific
2014-08-15 23:26:45 +00:00
else :
2014-10-21 18:55:39 +00:00
geocode = [ x for x in mapquest . geocode ( city ) ]
2014-08-15 23:26:45 +00:00
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
2015-10-30 20:57:45 +00:00
raise IMException ( " Je ne sais pas où se trouve %s . " % city )
2014-04-30 20:41:47 +00:00
else :
2015-10-30 20:57:45 +00:00
raise IMException ( " indique-moi un nom de ville ou des coordonnées. " )
2014-04-30 20:41:47 +00:00
2016-11-10 17:32:50 +00:00
def get_json_weather ( coords , lang = " en " , units = " auto " ) :
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 " ] :
2015-10-30 20:57:45 +00:00
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 " )
2014-05-05 16:45:13 +00:00
def cmd_coordinates ( msg ) :
2014-10-21 18:55:39 +00:00
if len ( msg . args ) < 1 :
2015-10-30 20:57:45 +00:00
raise IMException ( " indique-moi un nom de ville. " )
2014-05-05 16:45:13 +00:00
2014-10-21 18:55:39 +00:00
j = msg . args [ 0 ] . lower ( )
2015-02-11 17:12:39 +00:00
if j not in context . data . index :
2015-10-30 20:57:45 +00:00
raise IMException ( " %s n ' est pas une ville connue " % msg . args [ 0 ] )
2014-05-05 16:45:13 +00:00
2015-02-11 17:12:39 +00:00
coords = context . data . index [ j ]
2014-10-21 18:55:39 +00:00
return Response ( " Les coordonnées de %s sont %s , %s " % ( msg . args [ 0 ] , coords [ " lat " ] , coords [ " long " ] ) , channel = msg . channel )
2014-05-05 16:45:13 +00:00
2014-12-17 06:32:34 +00:00
2016-11-10 17:32:50 +00:00
@hook.command ( " alert " ,
keywords = {
" lang=LANG " : " change the output language of weather sumarry; default: en " ,
" units=UNITS " : " return weather conditions in the requested units; default: auto " ,
} )
2014-04-30 20:41:47 +00:00
def cmd_alert ( msg ) :
2014-08-15 23:26:45 +00:00
loc , coords , specific = treat_coord ( msg )
2016-11-10 17:32:50 +00:00
wth = get_json_weather ( coords ,
lang = msg . kwargs [ " lang " ] if " lang " in msg . kwargs else " en " ,
units = msg . kwargs [ " units " ] if " units " in msg . kwargs else " auto " )
2014-04-30 20:41:47 +00:00
2014-09-18 05:57:06 +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 " ] :
2017-07-05 22:36:12 +00:00
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
2014-12-17 06:32:34 +00:00
2016-11-10 17:32:50 +00:00
@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 " ,
" units=UNITS " : " return weather conditions in the requested units; default: auto " ,
} )
2014-04-30 20:41:47 +00:00
def cmd_weather ( msg ) :
2014-08-15 23:26:45 +00:00
loc , coords , specific = treat_coord ( msg )
2016-11-10 17:32:50 +00:00
wth = get_json_weather ( coords ,
lang = msg . kwargs [ " lang " ] if " lang " in msg . kwargs else " en " ,
units = msg . kwargs [ " units " ] if " units " in msg . kwargs else " auto " )
2014-04-30 20:41:47 +00:00
2014-09-18 05:57:06 +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 " ] :
2017-07-05 22:36:12 +00:00
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 ]
2014-05-05 08:40:07 +00:00
res . append_message ( " \x03 \x02 At %s h: \x03 \x02 %s " % ( format_timestamp ( int ( hour [ " time " ] ) , wth [ " timezone " ] , wth [ " offset " ] , ' % H ' ) , format_wth ( hour ) ) )
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 ]
2014-05-05 08:40:07 +00:00
res . append_message ( " \x03 \x02 On %s : \x03 \x02 %s " % ( format_timestamp ( int ( day [ " time " ] ) , wth [ " timezone " ] , wth [ " offset " ] , ' % A ' ) , format_forecast_daily ( day ) ) )
2014-04-30 20:41:47 +00:00
else :
res . append_message ( " I don ' t understand %s or information is not available " % specific )
else :
res . append_message ( " \x03 \x02 Currently: \x03 \x02 " + format_wth ( wth [ " currently " ] ) )
nextres = " \x03 \x02 Today: \x03 \x02 %s " % wth [ " daily " ] [ " data " ] [ 0 ] [ " summary " ]
if " minutely " in wth :
nextres + = " \x03 \x02 Next hour: \x03 \x02 %s " % wth [ " minutely " ] [ " summary " ]
nextres + = " \x03 \x02 Next 24 hours: \x03 \x02 %s \x03 \x02 Next week: \x03 \x02 %s " % ( wth [ " hourly " ] [ " summary " ] , wth [ " daily " ] [ " summary " ] )
res . append_message ( nextres )
for hour in wth [ " hourly " ] [ " data " ] [ 1 : 4 ] :
2014-05-05 08:40:07 +00:00
res . append_message ( " \x03 \x02 At %s h: \x03 \x02 %s " % ( format_timestamp ( int ( hour [ " time " ] ) , wth [ " timezone " ] , wth [ " offset " ] , ' % H ' ) ,
format_wth ( hour ) ) )
2014-04-30 20:41:47 +00:00
for day in wth [ " daily " ] [ " data " ] [ 1 : ] :
2014-05-05 08:40:07 +00:00
res . append_message ( " \x03 \x02 On %s : \x03 \x02 %s " % ( format_timestamp ( int ( day [ " time " ] ) , wth [ " timezone " ] , wth [ " offset " ] , ' % A ' ) ,
format_forecast_daily ( day ) ) )
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 )
2014-12-17 06:32:34 +00:00
2015-11-02 19:19:12 +00:00
@hook.ask ( )
2014-04-30 20:41:47 +00:00
def parseask ( msg ) :
2014-08-29 14:33:45 +00:00
res = gps_ask . match ( msg . text )
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 ( )
2014-09-18 05:57:06 +00:00
return Response ( " ok, j ' ai bien noté les coordonnées de %s " % res . group ( " city " ) ,
2014-04-30 20:41:47 +00:00
msg . channel , msg . nick )