User Tools

Site Tools


weatherbot

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
weatherbot [2014/08/23 16:47]
sekula [Source Code]
weatherbot [2014/08/30 23:23] (current)
sekula [Talking to WeatherBot]
Line 22: Line 22:
   * PyPump [[https://​github.com/​xray7224/​PyPump.git]]   * PyPump [[https://​github.com/​xray7224/​PyPump.git]]
   * LYNX (for converting HTML to plain text - some pump clients don't send messages in HTML, some do... this saved me having to write my own converter)   * LYNX (for converting HTML to plain text - some pump clients don't send messages in HTML, some do... this saved me having to write my own converter)
 +
 +===== Bug Reporting and Feature Requests =====
 +
 +  * [[community:​WeatherBotBugTracker]]
  
  
Line 32: Line 36:
 # Distributed under an Apache 2.0 license # Distributed under an Apache 2.0 license
 # For more information ,see  # For more information ,see 
-# http://​polari.us/​dokuwiki/​doku.php?​id=weatherbot#​license 
-# 
  
 import re import re
Line 41: Line 43:
 import sys import sys
 import time import time
-import subprocess+import subprocess ​
 import os import os
 +import unicodedata
 +
 +from datetime import datetime
  
 from pypump import PyPump, Client from pypump import PyPump, Client
Line 54: Line 59:
 # city,region search pattern # city,region search pattern
 citypattern = re.compile('​.*?​([A-Za-z]+,​.*)',​re.DOTALL) citypattern = re.compile('​.*?​([A-Za-z]+,​.*)',​re.DOTALL)
 +
 +tripletpattern = re.compile('​.*?​([A-Za-z\-\s]+,​\s{0,​1}[A-Za-z]+,​\s{0,​1}[A-Za-z]+).*',​re.DOTALL)
 +doubletpattern = re.compile('​.*?​([A-Za-z\-\s]+,​\s{0,​1}[A-Za-z]+).*',​re.DOTALL)
 +
 +for_tripletpattern = re.compile('​^.*?​\sfor %s' % (tripletpattern.pattern),​re.DOTALL)
 +in_tripletpattern = re.compile('​^.*?​\sin %s' % (tripletpattern.pattern),​re.DOTALL)
 +
 +for_doubletpattern = re.compile('​^.*?​\sfor %s' % (doubletpattern.pattern),​re.DOTALL)
 +in_doubletpattern = re.compile('​^.*?​\sin %s' % (doubletpattern.pattern),​re.DOTALL)
 +
  
 # Place the credentials below for the pump.io account # Place the credentials below for the pump.io account
 # These can be obtained by using: http://​polari.us/​dokuwiki/​doku.php?​id=navierstokes#​a_simple_program_to_authenticate_pypump_against_your_pumpio_instance # These can be obtained by using: http://​polari.us/​dokuwiki/​doku.php?​id=navierstokes#​a_simple_program_to_authenticate_pypump_against_your_pumpio_instance
  
-client_credentials = [] +client_credentials = ['​XXX',​ '​XXX'​
-client_tokens = []+client_tokens = ['​XXX',​ '​XXX'​]
  
 # Webfinger for your account # Webfinger for your account
-webfinger = "me@example.com"​+webfinger = "name@example.com"​
  
 # Important: define a log file that contains a list of activities to which # Important: define a log file that contains a list of activities to which
Line 83: Line 98:
  
     htmlfile.close()     htmlfile.close()
 +        ​
     try:     try:
         ascii_text = subprocess.check_output(["​lynx",​ "​--dump",​ "​-width 2048", "​-nolist",​ "/​tmp/​%d_msg.html"​ % (pid)])         ascii_text = subprocess.check_output(["​lynx",​ "​--dump",​ "​-width 2048", "​-nolist",​ "/​tmp/​%d_msg.html"​ % (pid)])
Line 102: Line 117:
         ​         ​
     return True     return True
 +
 +def KelvinToCelsius(temp_kelvin):​
 +    return temp_kelvin - 273.15
 +
 +def CelsiusToFarenheit(temp_celsius):​
 +    return temp_celsius*1.8 + 32.0
 +
 +def FarenheitToCelsius(temp_farenheit):​
 +    return (temp_farenheit - 32.0)/1.8
 +
 +def KPHToMPH(speed_kph):​
 +    return speed_kph/​0.6214
 +
 +def current_conditions(weather_data):​
 +    humidity = weather_data['​main'​]['​humidity'​]
 +    ​
 +    temp_kelvin = weather_data['​main'​]['​temp'​]
 +    temp_celsius = KelvinToCelsius(temp_kelvin)
 +    temp_farenheit = CelsiusToFarenheit(temp_celsius)
 +    ​
 +    wind_speed_kmh = weather_data['​wind'​]['​speed'​]
 +    wind_speed_mph = KPHToMPH(wind_speed_kmh)
 +    ​
 +    heat_index_farenheit = -42.379 + 2.04901523*temp_farenheit + 10.14333127*humidity - .22475541*temp_farenheit*humidity - .00683783*temp_farenheit*temp_farenheit - .05481717*humidity*humidity + .00122874*temp_farenheit*temp_farenheit*humidity + .00085282*temp_farenheit*humidity*humidity - .00000199*temp_farenheit*temp_farenheit*humidity*humidity
 +    heat_index_celsius = FarenheitToCelsius(heat_index_farenheit)
 +    ​
 +    response = ""​
 +    ​
 +    response += "<​p>​The weather in %s is currently: </​p>​\n"​ % weather_data["​name"​]
 +    response += "<​p><​img src=\"​http://​openweathermap.org/​img/​w/​%s.png\"/></​p>"​ % (weather_data['​weather'​][0]['​icon'​]) ​
 +    response += "<​ul>​\n"​
 +    response += "<​li>​ Temperature:​ %0.1fC (%0.1fF) </​li>​\n"​ % (temp_celsius,​ temp_farenheit)
 +    response += "<​li>​ Humidity: %0.1f%% </li> \n" % (humidity)
 +    response += "<​li>​ Heat Index: %0.1fC (%0.1fF) </​li>​\n"​ % (heat_index_celsius,​heat_index_farenheit) ​   ​
 +    response += "<​li>​ Wind Speed: %.1fkm/h (%.1fmph) </​li>​\n"​ % (wind_speed_kmh,​wind_speed_mph)
 +    response += "<​li>​ Conditions: %s - %s</​li>​ \n" % (weather_data['​weather'​][0]['​main'​],​ weather_data['​weather'​][0]['​description'​])
 +    response += "</​ul>​\n"​
 +
 +
 +    return response
 +
 +def forecast(weather_data):​
 +    list_of_data = weather_data['​list'​]
 +    response = ""​
 +
 +    response += "<​p>​The forecast for %s is: </​p>​\n"​ % weather_data["​city"​]["​name"​]
 +    response += "<​table style=\"​border-collapse:​ collapse;​border:​ 1px solid black;​\">​\n"​
 +    response += "<​tr><​td style=\"​border:​ 1px solid black;​\"><​b>​TIME</​b></​td><​td style=\"​border:​ 1px solid black;​\"><​b>​Temp</​b></​td><​td style=\"​border:​ 1px solid black;​\"><​b>​Humidity</​b></​td><​td style=\"​border:​ 1px solid black;​\"><​b>​Description</​b></​td></​tr>​\n"​
 +    for moment in list_of_data:​
 +        dt = datetime.fromtimestamp(moment["​dt"​])
 +        timestamp = dt.strftime('​%Y-%m-%d %H:​%M:​%S'​)
 +        temp_celsius = KelvinToCelsius(moment["​main"​]["​temp"​])
 +        temp_farenheit = CelsiusToFarenheit(temp_celsius)
 +        humidity = moment["​main"​]["​humidity"​]
 +        description = "%s - %s" % (moment["​weather"​][0]["​main"​],​ moment["​weather"​][0]["​description"​])
 +        icon = "​http://​openweathermap.org/​img/​w/​%s.png"​ % (moment["​weather"​][0]["​icon"​])
 +        response += "<​tr><​td style=\"​border:​ 1px solid black;​\">​%s</​td><​td style=\"​border:​ 1px solid black;​\">​%s C (%s F)</​td><​td style=\"​border:​ 1px solid black;​\">​%s%%</​td><​td style=\"​border:​ 1px solid black;​\"><​img src=\"​%s\"/>​%s</​td></​tr>"​ % (timestamp,​temp_celsius,​temp_farenheit,​humidity,​icon,​description)
 +    response += "</​table>"​
 +
 +    return response
  
  
Line 163: Line 238:
                 ​                 ​
                     # handle this content                     # handle this content
 +                    ​
                     text_content = HTMLToText(content)                     text_content = HTMLToText(content)
 +                    text_content = text_content.lower()
  
-                    ​search_result ​citypattern.match(text_content) +                    ​query -1 
-               ​ +                    if text_content.find('​current'​) != -1 or \ 
-                    ​if search_result:+                       text_content.find('​conditions'​!= -1 or \ 
 +                       text_content.find('​currently'​) != -1: 
 +                        query = 0 
 +                    ​elif text_content.find('​forecast'​) != -1: 
 +                        query = 1 
 +                        pass 
 +                    else: 
 +                        # default to current conditions 
 +                        query = 0
  
-                        print "%s sent a weather request, which I will now process..."​ % (activity.obj.author) 
  
 +                    search_result = in_tripletpattern.match(text_content)
 +                    if not search_result:​
 +                        print " ... Search failed for in + triplet"​
 +                        search_result = for_tripletpattern.match(text_content)
 +                    if not search_result:​
 +                        print " ... Search failed for for + triplet"​
 +                        search_result = tripletpattern.match(text_content)
 +                        ​
 +                    if not search_result:​
 +                        print " ... Search failed for pure triplet"​
 +                        search_result = in_doubletpattern.match(text_content)
 +                    if not search_result:​
 +                        print " ... Search failed for in + doublet"​
 +                        search_result = for_doubletpattern.match(text_content)
 +                    if not search_result:​
 +                        print " ... Search failed for for + doublet"​
 +                        search_result = doubletpattern.match(text_content)
 +                    if not search_result:​
 +                        print " ... Search failed for pure doublet"​
 +                        pass
 +
 +                    if query >= 0 and search_result:​
                         cityrequest = search_result.group(1)                         cityrequest = search_result.group(1)
 +
 +                        query_type = "​current conditions"​
 +                        if query == 1:
 +                            query_type = "​forecast"​
 +                            pass
 +                        ​
 +                        author = ""​
 +                        if type(activity.obj.author) == Person:
 +                            author = activity.obj.author.display_name
 +                        elif type(activity.obj.author) == unicode:
 +                            author = unicodedata.normalize('​NFKD',​ activity.obj.author).encode('​ascii','​ignore'​)
 +                        else:
 +                            author = activity.obj.author
 +                            pass
 +                                                ​
 +                        print "%s sent a weather request for the %s for %s, which I will now process..."​ % (author, query_type, cityrequest)
                         ​                         ​
                         # clean up poor formatting in content                         # clean up poor formatting in content
Line 185: Line 307:
                         ​                         ​
                             c = pycurl.Curl()                             c = pycurl.Curl()
-                            c.setopt(c.URL,​ '​http://​api.openweathermap.org/​data/​2.5/​weather?​q=%s'​ % (cityrequest))+                            ​if query == 0: 
 +                                # current conditions 
 +                                ​c.setopt(c.URL,​ '​http://​api.openweathermap.org/​data/​2.5/​weather?​q=%s'​ % (cityrequest)) 
 +                            elif query == 1: 
 +                                # forecast 
 +                                c.setopt(c.URL,​ '​http://​api.openweathermap.org/​data/​2.5/​forecast?​q=%s'​ % (cityrequest)) 
 +                                pass
                             c.setopt(c.WRITEFUNCTION,​ curl_buffer.write)                             c.setopt(c.WRITEFUNCTION,​ curl_buffer.write)
  
Line 231: Line 359:
                         try:                         try:
  
-                            ​humidity ​= weather_data['​main'​]['​humidity'​]+                            ​if query == 0: 
 +                                response = current_conditions(weather_data) 
 +                            elif query == 1: 
 +                                response = forecast(weather_data
 +                             
 +                                pass
  
-                            temp_kelvin = weather_data['​main'​]['​temp'​] 
-                            temp_celsius = temp_kelvin - 273.15 
-                            temp_farenheit = temp_celsius*1.8 + 32.0 
-                            ​ 
-                            wind_speed_kmh = weather_data['​wind'​]['​speed'​] 
-                            wind_speed_mph = wind_speed_kmh/​0.6214 
-                            ​ 
-                            response = ""​ 
-                            ​ 
-                            response += "<​p>​The weather in %s is currently: </​p>​\n"​ % weather_data["​name"​] 
-                            response += "<​p><​img src=\"​http://​openweathermap.org/​img/​w/​%s.png\"/></​p>"​ % (weather_data['​weather'​][0]['​icon'​]) ​ 
-                            response += "<​ul>​\n"​ 
-                            response += "<​li>​ Temperature:​ %0.1fC (%0.1fF) </​li>​\n"​ % (temp_celsius,​ temp_farenheit) 
-                            response += "<​li>​ Humidity: %0.1f%% </li> \n" % (humidity) 
-                            response += "<​li>​ Wind Speed: %.1fkm/h (%.1fmph) </​li>​\n"​ % (wind_speed_kmh,​wind_speed_mph) 
-                            response += "<​li>​ Conditions: %s - %s</​li>​ \n" % (weather_data['​weather'​][0]['​main'​],​ weather_data['​weather'​][0]['​description'​]) 
-                            response += "</​ul>​\n"​ 
-                        ​ 
                             my_reply = pump.Comment(response)                             my_reply = pump.Comment(response)
                             my_reply.cc = cc                             my_reply.cc = cc
 +                            my_reply.cc.append( pump.Public )
 +                            ​
                             try:                             try:
                                 activity.obj.comment(my_reply)                                 activity.obj.comment(my_reply)
Line 260: Line 377:
                                 print " ​  ... PyPumpException - ERROR - I will try to get this request next time!"                                 print " ​  ... PyPumpException - ERROR - I will try to get this request next time!"
                                 continue                                 continue
 +
                         except KeyError:                         except KeyError:
                             response = "<​p>​There was a problem getting the response from OpenWeatherMaps.org. It could be them; or, it could be that you did not give me a valid \"​City,​Region\"​ string. Please try again.</​p>"​                             response = "<​p>​There was a problem getting the response from OpenWeatherMaps.org. It could be them; or, it could be that you did not give me a valid \"​City,​Region\"​ string. Please try again.</​p>"​
Line 269: Line 387:
                             print " ​  ... got bad data from the API, it seems. Letting the user know."                             print " ​  ... got bad data from the API, it seems. Letting the user know."
                             continue                             continue
-                            ​ 
                         pass                         pass
                     pass                     pass
Line 281: Line 398:
 process_log.close() process_log.close()
     ​     ​
 +
  
  
 </​code>​ </​code>​
  
 +===== Talking to WeatherBot =====
 +
 +WeatherBot (e.g. [[https://​hub.polari.us/​weather]]) can now take various request forms. Sending these:
 +
 +<​code>​
 +New York,NY,US
 +or
 +Stockholm,​SE
 +</​code>​
 +
 +results in the original behavior - you get the current conditions.
 +
 +Sending any of these:
 +
 +<​code>​
 +Current conditions in Jefferson City,MO,US
 +Currently Uppsala,SE
 +Current in London,UK
 +Conditions for Moscow,RU
 +etc.
 +</​code>​
 +
 +also results in the current conditions.
 +
 +But sending any of these:
 +
 +<​code>​
 +Forecast for London,UK
 +Forecast in Dallas,​TX,​US
 +</​code>​
 +
 +will return a table of forecast data for the city in question, going out 3 days. This is just a first attempt to do simple request processing based on language, and, yes, it's only in English for now. But now you can at least get a forecast!
 +
 +{{ :​weatherbot_forecast.png?​direct&​300 |}}
  
weatherbot.1408826829.txt.gz · Last modified: 2014/08/23 16:47 (external edit)