Getting weather reports using Google and Python

Discussion in 'Linux Other' started by ehansen, Jul 10, 2013.

  1. ehansen

    ehansen New Member Staff Writer

    Messages:
    115
    Likes Received:
    11
    Trophy Points:
    0
    My fiance is always asking me to give her a basic weather report on days that she works, and while I have no problem doing such things I knew there were ways to automate the process. I previously wrote a script for my business that parsed text messages being sent to a Google Voice number using PHP, and so I took that knowledge and used it in Python to make the job a lot easier.

    Requirements

    This guide is going to figure the following statements are true before you continue on:

    • You are using Python 2.7.3 (this hasn't been tested with anything earlier or later)
    • You have a Google Voice number
    • Said Google Voice number forwards text messages to a specific e-mail address (GMail is assumed here, no others have been tested)
    • You have a Weather Underground API key (the free service works just fine for what we are going to do here)
    • You have the Requests Python module installed (pip install requests). You can use httplib or urllib, but I always prefer Requests
    The Start

    Before we do anything, we'll specify some modules we'll need for this to work:


    Code:
    import imaplib
    import smtplib
    import email
    import json
    import requests
    To make things easier for myself, I have a file in my "everything not related to work" GitHub repo that has configurations for various aspects (e-mail accounts, API keys, etc...). It's got nothing in it but a JSON object so I use this function to deal with it:

    Code:
    f = open('balance.sfu', 'r') data = json.loads(f.read()) f.close()
    Since we're going to be using GMail, we need to get the variables from the configure script:

    Code:
    wu_api = data['weather_api'] gu = "%s@gmail.com" % (data['gmail']['user']) gp = data['gmail']['pass']
    The Weather Function

    Now, we will have a function to call the API each time a text message is found. If you want to take this one step further you could either cache the JSON in something like Redis or in a class structure instead of making a new API call each time. However, that will be homework.

    Code:
    def get_msg(state="MI", zc=48122):    import requests    import json          req = "http://api.wunderground.com/api/"+wu_api+"/conditions/q/%s/%d.json" % (state, zc)          resp = requests.get(req)          resp = resp.json['current_observation']          msg = "It is %s at %s with humidity of %s (dewpoint: %s)." % (resp['icon'], resp['temperature_string'], resp['relative_humidity'], resp['dewpoint_string'])    msg = "%s  %s are the winds," % (msg, resp['wind_string'])    msg = "%s providing a wind chill of %s and heat index of %s." % (msg, resp['windchill_string'], resp['feelslike_string'])    msg = "%s  Visibility is %s miles." % (msg, resp['visibility_mi'])    msg = "%s  Data was fetched from %s at %s." % (msg, resp['observation_location']['full'], resp['local_time_rfc822'])          return msg
    Overall this is pretty simple to deal with. You can pass a zip code and/or state to Weather Underground (WU) and it will return the following JSON object:

    Code:
    {    "current_observation": {        "UV": "1",          "dewpoint_c": 14,          "dewpoint_f": 57,          "dewpoint_string": "57 F (14 C)",          "display_location": {            "city": "Melvindale",              "country": "US",              "country_iso3166": "US",              "elevation": "176.00000000",              "full": "Melvindale, MI",              "latitude": "42.27963257",              "longitude": "-83.18051147",              "state": "MI",              "state_name": "Michigan",              "zip": "48122"        },          "estimated": {},          "feelslike_c": "18.4",          "feelslike_f": "65.1",          "feelslike_string": "65.1 F (18.4 C)",          "forecast_url": "http://www.wunderground.com/US/MI/Melvindale.html",          "heat_index_c": "NA",          "heat_index_f": "NA",          "heat_index_string": "NA",          "history_url": "http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=KMILINCO9",          "icon": "cloudy",          "icon_url": "http://icons-ak.wxug.com/i/c/k/cloudy.gif",          "image": {            "link": "http://www.wunderground.com",              "title": "Weather Underground",              "url": "http://icons-ak.wxug.com/graphics/wu2/logo_130x80.png"        },          "local_epoch": "1351012005",          "local_time_rfc822": "Tue, 23 Oct 2012 13:06:45 -0400",          "local_tz_long": "America/New_York",          "local_tz_offset": "-0400",          "local_tz_short": "EDT",          "ob_url": "http://www.wunderground.com/cgi-bin/findweather/getForecast?query=42.241833,-83.183273",          "observation_epoch": "1351010968",          "observation_location": {            "city": "Detroit Ave. & Fort Park, Lincoln Park",              "country": "US",              "country_iso3166": "US",              "elevation": "587 ft",              "full": "Detroit Ave. & Fort Park, Lincoln Park, Michigan",              "latitude": "42.241833",              "longitude": "-83.183273",              "state": "Michigan"        },          "observation_time": "Last Updated on October 23, 12:49 PM EDT",          "observation_time_rfc822": "Tue, 23 Oct 2012 12:49:28 -0400",          "precip_1hr_in": "-999.00",          "precip_1hr_metric": " 0",          "precip_1hr_string": "-999.00 in ( 0 mm)",          "precip_today_in": "0.27",          "precip_today_metric": "7",          "precip_today_string": "0.27 in (7 mm)",          "pressure_in": "29.99",          "pressure_mb": "1016",          "pressure_trend": "+",          "relative_humidity": "75%",          "solarradiation": "0",          "station_id": "KMILINCO9",          "temp_c": 18.4,          "temp_f": 65.1,          "temperature_string": "65.1 F (18.4 C)",          "visibility_km": "16.1",          "visibility_mi": "10.0",          "weather": "Overcast",          "wind_degrees": 180,          "wind_dir": "South",          "wind_gust_kph": 0,          "wind_gust_mph": 0,          "wind_kph": 4.3,          "wind_mph": 2.7,          "wind_string": "From the South at 2.7 MPH",          "windchill_c": "NA",          "windchill_f": "NA",          "windchill_string": "NA"    },      "response": {        "features": {            "conditions": 1        },          "termsofService": "http://www.wunderground.com/weather/api/d/terms.html",          "version": "0.1"    } }
    The object out of this we are most interested in is the "current_observation", which we get by simply doing

    Code:
    resp = resp.json['current_observation']
    Then we just pull a bunch of different variables and store it into 'msg', then ship it off.

    The real fun comes in checking the texts and shipping it off:

    Code:
    imap = imaplib.IMAP4_SSL('imap.gmail.com') imap.login(gu, gp) imap.select('inbox')  smtp = smtplib.SMTP_SSL('smtp.gmail.com',465) smtp.login(gu, gp)
    First we start off by connecting to the IMAP server via SSL and selecting the inbox. Then we establish a connection to GMail's SMTP server via SSL.

    Code:
    res, data = imap.uid('search', None, '((FROM "*.txt.voice.google.com") (UNSEEN))')
    At the time of writing, Google Voice forwards text messages from "@txt.voice.google.com", so here we search for any e-mails that are unread ("UNSEEEN") from Google Voice that are text messages, and return the unique message ID's (instead of message numbers) in data (res is the result of the call).

    Now we cycle through each unique ID like so:

    Code:
    for msg_id in data:    body = imap.uid('fetch', msg_id, '(RFC822)')[1][0][1]    em = email.message_from_string(body)          users, domain = email.utils.parseaddr(em['From'])[1].split('@')    parts = users.split(".")          to = em['From']
    We then fetch the RFC 822 formatted e-mail body (which you typically see when you are viewing the original body in GMail), and then use Python's email library to parse the e-mail into a dictionary, making it easier to work with the headers. The recipient address will be the same as the from address so we just need to get the 'From' field of the message.

    Next, we're done with the message so we'll mark it for deletion on GMail's server and then tell GMail to delete any e-mails marked as such:

    Code:
        imap.uid('STORE', msg_id, '+FLAGS', '(Deleted)')    imap.expunge()
    Next we set some headers to send to GMail's servers so they at least know what's going on, then send the e-mail via SMTP:


    Code:
        headers = ["From: " + em['From'], "Subject: SMS from %s" % (email.utils.parseaddr(em['From'])[0]),                "To: "+to, "MIME-Version: 1.0", "Content-Type: text/html"]    headers = "rn".join(headers)          smtp.sendmail(em['From'], to, headers + "rnrn" + body_msg)
    After all the e-mails are sent, we will close any open connections because we're nice programmers :)

    Code:
    smtp.quit() imap.close()
    The ending text message will look something like this:

    DevynCJohnson likes this.

Share This Page