Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

basics

  • Loading branch information...
commit 8ffb8f19784127047114da88333b9d5f8be2e627 0 parents
@softprops authored
1  Procfile
@@ -0,0 +1 @@
+web python app.py
18 README.md
@@ -0,0 +1,18 @@
+# Muxup
+
+A Meetup API flask example
+
+## install
+
+This app depends on [Flask][flask]
+
+ easy_install Flask
+
+Before starting the application be sure to export the following environment variables
+
+ export CLIENT_ID=YOUR_REGISTERED_MEETUP_OAUTH_CONSUMER_KEY
+ export CLIENT_SECRET=YOUR_REGISTED_MEETUP_OAUTH_CLIENT_SECRET
+ export COOKIE_SECRET=RANDOM_STRING_OF_CHARS_USED_TO_SIGN_COOKIES
+
+
+[flask]: http://flask.pocoo.org/
104 app.py
@@ -0,0 +1,104 @@
+from flask import (Flask, session, redirect, url_for,
+ escape, render_template, request)
+from os import getenv
+from json import dumps as to_json, loads as from_json
+from meetup import (client, url_for_authentication, request_access_token,
+ refresh_access_token, MeetupNotAuthorized, MeetupBadRequest)
+
+app = Flask(__name__)
+
+@app.route('/')
+def index():
+ ''' let's make this as simple as possbile.
+ are you or are you not oauthenticated?
+ '''
+ if connected():
+ return render_template('connected.html', current_user = mu().current_user())
+ else:
+ return render_template('index.html')
+
+@app.route('/topics/<topic>')
+def events(topic):
+ if connected():
+ events = mu().open_events({ 'topic': topic, 'page': 10 })
+ current_user = mu().current_user()
+ return render_template('events.html', topic = topic,
+ events = events['results'], current_user = current_user)
+ else:
+ return redirect(url_for('index'))
+
+@app.route('/signout')
+def signout():
+ ''' let's go back to where we started
+ '''
+ session.pop('credentials', None)
+ return redirect(url_for('index'))
+
+@app.route('/connect')
+def connect():
+ ''' let's connect with meetup.com
+ '''
+ return redirect(url_for_authentication(url_for('auth', _external = True)))
+
+@app.route('/auth')
+def auth():
+ ''' meetup.com will redirect the user here after
+ they were prompted for authentication.
+ if the user authorized this application, we
+ should request an access token
+ '''
+ if request.args.get('error'):
+ return redirect(url_for('denied'))
+ else:
+ code, state = map(lambda k: request.args.get(k), ['code', 'state'])
+ session['credentials'] = request_access_token(
+ code, url_for('auth', _external = True))
+ return redirect(url_for('index'))
+
+@app.route('/denied')
+def denied():
+ return render_template('denied.html')
+
+@app.errorhandler(404)
+def not_found(error):
+ return render_template('not_found.html')
+
+@app.errorhandler(500)
+def server_error(error):
+ return render_template('app_error.html', error = 'Server error %s' % error)
+
+@app.errorhandler(MeetupNotAuthorized)
+def meetup_not_authorized(error):
+ try:
+ fresh = refresh_access_token(session['credentials']['refresh_token'])
+ session['credentials'] = fresh
+ return render_template('app_error.html', error = 'had to freshin up')
+ except Exception, e:
+ session.pop('credentials', None)
+ #return render_template('app_error.html', error = 'could not refresh %s' % e)
+ return redirect(url_for('signout'))
+
+@app.errorhandler(MeetupBadRequest)
+def meetup_bad_request(error):
+ return render_template('app_error.html', error = error)
+
+app.secret_key = getenv(
+ 'COOKIE_SECRET',
+ '\xd1\x9fQ=\xd7:\x89|\xce\x02\x93\xb5O\x1a\x8e\xf1\xccy\x92tu\\PF')
+
+# helpers
+
+def connected():
+ ''' return True if the current user is connected
+ to meetup, otherwise return False
+ '''
+ return 'credentials' in session
+
+
+def mu():
+ ''' shorthand for getting a configured meetup client
+ '''
+ return client(session['credentials']['access_token'])
+
+if __name__ == '__main__':
+ app.run()
137 meetup.py
@@ -0,0 +1,137 @@
+from os import getenv
+from urllib import urlencode
+from urllib2 import Request, urlopen, quote, URLError, HTTPError
+from datetime import datetime
+from json import dumps as to_json, loads as from_json
+
+MU_HOST = getenv('MU_HOST', 'https://secure.meetup.com')
+AUTHENTICATION_URI = '%s/oauth2/authorize' % MU_HOST
+TOKEN_URI = '%s/oauth2/access' % MU_HOST
+API_HOST = getenv('API_HOST', 'https://api.meetup.com')
+CLIENT_ID = getenv('CLIENT_ID')
+CLIENT_SECRET = getenv('CLIENT_SECRET')
+VERSION = '0.1.0'
+USER_AGENT = 'MeetupFlask/%s' % VERSION
+
+if not CLIENT_ID or not CLIENT_SECRET: raise Exception(
+ 'CLIENT_ID and CLIENT_SECRET must be set as env vars'
+)
+
+def url_for_authentication(redirect_uri, response_type='code',
+ state=str(datetime.now()), scopes=[]):
+ ''' return the configured uri for
+ acquireing user authorization for oauth2
+ '''
+ uri = "%s?client_id=%s&response_type=%s&state=%s&redirect_uri=%s" % (
+ AUTHENTICATION_URI,
+ CLIENT_ID,
+ response_type,
+ quote(state),
+ quote(redirect_uri))
+ return scopes and "%s&scope=%s" % (uri, '+'.join(scopes)) or uri
+
+def request_access_token(code, redirect_uri):
+ ''' make a request for an access token given
+ an access grant
+ '''
+ req = Request(TOKEN_URI,
+ data = urlencode({
+ 'client_id': CLIENT_ID,
+ 'client_secret': CLIENT_SECRET,
+ 'grant_type': 'authorization_code',
+ 'code': code,
+ 'redirect_uri': redirect_uri }),
+ headers = {
+ 'User-Agent' : USER_AGENT
+ })
+ resp = urlopen(req).read()
+ return from_json(resp)
+
+def refresh_access_token(refresh_token):
+ ''' request a new set of oauth2 credentials given
+ a previously acquired refresh_token
+ '''
+ req = Request(TOKEN_URI,
+ data = urlencode({
+ 'client_id': CLIENT_ID,
+ 'client_secret': CLIENT_SECRET,
+ 'grant_type': 'refresh_token',
+ 'refresh_token': refresh_token,
+ }),
+ headers = {
+ 'User-Agent': USER_AGENT
+ })
+ resp = urlopen(req).read()
+ print resp
+ return from_json(resp)
+
+def client(access_token):
+ ''' returns a new configured meetup api client
+ '''
+ return Client(API_HOST, access_token)
+
+class MeetupNotAuthorized(Exception):
+ ''' Represents an exception thrown when an api request
+ is rejected due to invalid authorization
+ '''
+ pass
+
+class MeetupBadRequest(Exception):
+ ''' Represents an exception throw when an api request
+ is rejected for being malformed
+ '''
+ pass
+
+class Client():
+ ''' rest client for api.meetup.com
+ '''
+
+ def __init__(self, host, access_token, encoding = 'ISO-8859-1'):
+ self.host = host
+ self.access_token = access_token
+ self.encoding = encoding
+
+ def with_access(self, params):
+ return dict(params.items() + { 'access_token': self.access_token }.items())
+
+ def client_headers(self):
+ return { 'User-Agent': USER_AGENT }
+
+ def get(self, path, params = {}):
+ try:
+ url = "%s%s?%s" % (self.host, path, urlencode(self.with_access(params)))
+ print url
+ return from_json(urlopen(Request(url, headers = self.client_headers())).read(), encoding = self.encoding)
+ except HTTPError, e:
+ if 401 == e.code: raise MeetupNotAuthorized(e.read())
+ if 400 == e.code: raise MeetupBadRequest(e.read())
+ except URLError, e:
+ raise MeetupBadRequest("malformed url %s" % e.reason)
+ except Exception, e:
+ raise MeetupNotAuthorized('not authorized to request %s: %s' % (path, e))
+
+ def post(self, path, params = {}):
+ try:
+ url = "%s%s" % (self.host, path)
+ return from_json(urlopen(Request(url,
+ data = urlencode(self.with_access(params)),
+ headers = self.client_headers())).read(), encoding = self.encoding)
+ except HTTPError, e:
+ if 401 == e.code: raise MeetupNotAuthorized(e.read())
+ if 400 == e.code: raise MeetupBadRequest(e.read())
+ raise e
+ except URLError, e:
+ raise MeetupBadRequest("malformed url %s" % e.reason)
+ except Exception, e:
+ raise MeetupNotAuthorized('not authorized to request %s: %s' % (path, e))
+
+ def current_user(self):
+ ''' return the user that authorized this client
+ '''
+ return self.get('/2/member/self')
+
+ def open_events(self, params):
+ ''' return events that are in public in groups
+ and public to rsvp to
+ '''
+ return self.get('/2/open_events', params)
BIN  meetup.pyc
Binary file not shown
1  requirements.txt
@@ -0,0 +1 @@
+Flask==0.7.2
26 static/css/app.css
@@ -0,0 +1,26 @@
+body {
+ padding-top: 60px;
+}
+
+#options {
+ list-style: none;
+ padding:0;
+ margin:0;
+}
+
+#options a {
+ font-size:60px;
+ padding:.25em;
+ line-height:1em;
+ display:block;
+ text-shadow:0 1px #ccc;
+ color:#fff;
+}
+
+
+#options li:nth-child(6n+1) a { background:#FF18A3; }
+#options li:nth-child(6n+2) a { background:#8AF370; }
+#options li:nth-child(6n+3) a { background:#31FFFF; }
+#options li:nth-child(6n+4) a { background:#DD30FF; }
+#options li:nth-child(6n+5) a { background:#EEFF30; }
+#options li:nth-child(6n+6) a { background:#FF305A; }
5 templates/app_error.html
@@ -0,0 +1,5 @@
+{% extends "layout.html" %}
+{% block body %}
+<h1>Opps!<h1><h2> We made a mistake</h2>
+<p><pre>{{ error }}</pre></p>
+{% endblock %}
9 templates/connected.html
@@ -0,0 +1,9 @@
+{% extends "layout.html" %}
+{% block body %}
+<h1>What are you up for?</h1>
+<ul id="options">
+ {% for topic in current_user.topics %}
+ <li><a href="/topics/{{ topic.urlkey }}">{{ topic.name }}</a></li>
+ {% endfor %}
+</ul>
+{% endblock %}
5 templates/denied.html
@@ -0,0 +1,5 @@
+{% extends "layout.html" %}
+{% block body %}
+<h1>Dang!<h1><h2> You got denied.</h2>
+<p>Maybe some other time. Go <a href="/">home</a></p>
+{% endblock %}
9 templates/events.html
@@ -0,0 +1,9 @@
+{% extends "layout.html" %}
+{% block body %}
+<h1>of the topic of {{ topic }}</h1>
+<ul id="options">
+ {% for event in events %}
+ <li><a href="{{ event.event_url }}">{{ event.name }}</a></li>
+ {% endfor %}
+</ul>
+{% endblock %}
8 templates/index.html
@@ -0,0 +1,8 @@
+{% extends "layout.html" %}
+{% block body %}
+<h1>Muxup</h1>
+<p>Remember <a href="http://muxtape.com/">muxtape</a>? This is muxtape for your Meetup interests.</p>
+<p>
+ <a class="btn primary large" href="/connect">Connect with Meetup &raquo;</a>
+</p>
+{% endblock %}
27 templates/layout.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+ <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
+ <title>Muxup</title>
+ <head>
+ <link rel="stylesheet" href="http://twitter.github.com/bootstrap/1.3.0/bootstrap.min.css">
+ <link rel="stylesheet" type="text/css" href="/static/css/app.css" />
+ </head>
+ <body>
+ <div class="topbar">
+ <div class="fill">
+ <div class="container">
+ <a class="brand" href="/">Muxup</a>
+ {% if current_user %}
+ <ul class="nav">
+ <li><a href="/signout">Signout as {{ current_user.name }}</a></li>
+ </ul>
+ {% endif %}
+ </div>
+ </div>
+ </div>
+ <div class="container">
+ <div class="hero-unit">
+ {% block body %} {% endblock %}
+ </div>
+ </body>
+</html>
5 templates/not_found.html
@@ -0,0 +1,5 @@
+{% extends "layout.html" %}
+{% block body %}
+ <h1>Opps!<h1><h2> You made a wrong turn</h2>
+ <p>Try turning around at the <a href="/">next exit</a></p>
+{% endblock %}
Please sign in to comment.
Something went wrong with that request. Please try again.