2014-04-30 20:41:47 +00:00
# coding=utf-8
2014-08-27 23:39:31 +00:00
""" The weather module """
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
2015-06-03 20:07:06 +00:00
URL_DSAPI = " https://api.forecast.io/forecast/ %s / %% s, %% 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 help_full ( ) :
return " !weather /city/: Display the current weather in /city/. "
def fahrenheit2celsius ( temp ) :
return int ( ( temp - 32 ) * 50 / 9 ) / 10
2014-12-17 06:32:34 +00:00
2014-04-30 20:41:47 +00:00
def mph2kmph ( speed ) :
return int ( speed * 160.9344 ) / 100
2014-12-17 06:32:34 +00:00
2014-04-30 20:41:47 +00:00
def inh2mmh ( size ) :
return int ( size * 254 ) / 10
def format_wth ( wth ) :
return ( " %s °C %s ; precipitation ( %s %% chance) intensity: %s mm/h; relative humidity: %s %% ; wind speed: %s km/h %s °; cloud coverage: %s %% ; pressure: %s hPa; ozone: %s DU " %
(
fahrenheit2celsius ( wth [ " temperature " ] ) ,
wth [ " summary " ] ,
int ( wth [ " precipProbability " ] * 100 ) ,
inh2mmh ( wth [ " precipIntensity " ] ) ,
int ( wth [ " humidity " ] * 100 ) ,
mph2kmph ( wth [ " windSpeed " ] ) ,
wth [ " windBearing " ] ,
int ( wth [ " cloudCover " ] * 100 ) ,
int ( wth [ " pressure " ] ) ,
int ( wth [ " ozone " ] )
) )
2014-12-17 06:32:34 +00:00
2014-04-30 20:41:47 +00:00
def format_forecast_daily ( wth ) :
return ( " %s ; between %s - %s °C; precipitation ( %s %% chance) intensity: maximum %s mm/h; relative humidity: %s %% ; wind speed: %s km/h %s °; cloud coverage: %s %% ; pressure: %s hPa; ozone: %s DU " %
(
wth [ " summary " ] ,
fahrenheit2celsius ( wth [ " temperatureMin " ] ) , fahrenheit2celsius ( wth [ " temperatureMax " ] ) ,
int ( wth [ " precipProbability " ] * 100 ) ,
inh2mmh ( wth [ " precipIntensityMax " ] ) ,
int ( wth [ " humidity " ] * 100 ) ,
mph2kmph ( wth [ " windSpeed " ] ) ,
wth [ " windBearing " ] ,
int ( wth [ " cloudCover " ] * 100 ) ,
int ( wth [ " pressure " ] ) ,
int ( wth [ " ozone " ] )
) )
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
def get_json_weather ( coords ) :
2015-06-03 20:07:06 +00:00
wth = web . getJSON ( URL_DSAPI % ( float ( coords [ 0 ] ) , float ( coords [ 1 ] ) ) )
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
2015-11-02 19:19:12 +00:00
@hook.command ( " alert " )
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 )
2014-04-30 20:41:47 +00:00
wth = get_json_weather ( coords )
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 " ] :
2014-05-05 08:40:07 +00:00
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 " , " " ) ) )
2014-04-30 20:41:47 +00:00
return res
2014-12-17 06:32:34 +00:00
2015-11-02 19:19:12 +00:00
@hook.command ( " météo " )
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 )
2014-04-30 20:41:47 +00:00
wth = get_json_weather ( coords )
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 " ] :
2014-05-05 08:40:07 +00:00
alert_msgs . append ( " \x03 \x02 %s \x03 \x02 expire on %s " % ( alert [ " title " ] , format_timestamp ( int ( alert [ " expires " ] ) , wth [ " timezone " ] , wth [ " offset " ] ) ) )
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 )