Park Life

Free music and movies in Minneapolis

parks via text messaging

Image credit: http://linden-hills.com/

Text

What is this?

An application that responds to text messages by sending information about free music and movie events in Minneapolis parks.

Text FUN to (763) 273-5353

 

Tools used:

  • Python 
  • Google App Engine
  • Google Cloud Datastore
  • Twilio
  • Python libraries
    flask, icalendar, pytz

Creating the project

  1. Go to https://console.developers.google.com/project

  2. Click on "Create Project"

  3. Select "Try App Engine"

  4. Download the sample project

  5. Install the Google SDK if needed

Getting the data

  • Download calendar from Minneapolis Parks website
  • Convert from iCalendar format to JSON
from json import JSONEncoder
import json
from datetime import datetime
from icalendar import Calendar, vDDDTypes, Event

class ICalendarEncoder(JSONEncoder):
    def default(self, obj, markers=None):

        try:
            if obj.__module__.startswith("icalendar.prop"):
                return (obj.to_ical())
        except AttributeError:
            pass

        if isinstance(obj, datetime):
            return (obj.now().strftime('%Y-%m-%dT%H:%M:%S'))

        return JSONEncoder.default(self,obj)    


cal = Calendar.from_ical(open('basic.ics','rb').read())

event_list = []
for event in cal.walk(name="VEVENT"):
    suspect = json.dumps(event, cls=ICalendarEncoder)
    working = json.loads(suspect)
    event_list.append(working)
    
with open('cal.json', 'w') as f:
    cal_js = json.dumps(event_list)
    f.write(cal_js)

Creating the data model

# models.py
from google.appengine.ext import ndb

class Event(ndb.Model):
    summary = ndb.StringProperty()
    description = ndb.StringProperty()
    start = ndb.StringProperty()
    end = ndb.StringProperty()
    date = ndb.StringProperty()
    location = ndb.StringProperty()
    uid = ndb.StringProperty()

Loading into a database

# load.py
from flask import Flask
app = Flask(__name__)
app.config['DEBUG'] = True

import json
from models import Event
from util import utc_to_central, time_to_string

@app.route('/load')
def load():
    with open('cal.json') as f:
        events = json.load(f)
    for data in events:
        if 'RRULE' in data:  # Recurring event
            continue
        start = utc_to_central(data['DTSTART'])
        end = utc_to_central(data['DTEND'])
        date = "%d%02d%02d" % (start.year, start.month, start.day)
        start_time = time_to_string(start)
        end_time = time_to_string(end)

        event = Event(
            summary = data['SUMMARY'].strip(),
            description = data['DESCRIPTION'].strip(),
            date = date,
            start = start_time,
            end = end_time,
            location = data['LOCATION'].strip(),
            uid = data['UID'])
        event.put()
    return 'OK'

Utility functions

from datetime import datetime
from pytz import timezone
utc = timezone('utc')
central = timezone('US/Central')

def utc_to_central(s):
    dt = datetime.strptime(s, '%Y%m%dT%H%M%SZ')
    dt = utc.localize(dt)
    return dt.astimezone(central)

def today():
    dt = datetime.utcnow()
    dt = utc.localize(dt)
    dt = dt.astimezone(central)
    return datetime.strftime(dt, '%Y%m%d')

def time_to_string(t):
    s = str(1 + ((t.hour - 1) % 12))
    if t.minute:
        s += ':%02d' % t.minute
    if t.hour > 11:
        s += 'pm'
    else:
        s += 'am'
    return s

Responding to a text

# main.py
from flask import Flask
app = Flask(__name__)
app.config['DEBUG'] = True

from util import today
from models import Event
from google.appengine.ext import ndb
import twilio.twiml

@app.route('/message', methods=['GET', 'POST'])
def reply():
    query = Event.query(Event.date == today())
    messages = []
    for event in query:
        messages.append('%s %s (%s)' % 
                        (event.start, event.summary, event.location))
        response = twilio.twiml.Response()
        if len(messages) == 0:
            response.message('No events today')
        else:
            response.message(' | '.join(messages))
    return str(response)
  1. Purchase a Twilio phone number
  2. Set the SMS request url to http://your.site/message

Park Life

By David Radcliffe

Park Life

  • 1,070