Skip to content
This repository has been archived by the owner on Feb 8, 2018. It is now read-only.

Commit

Permalink
Can now tip to a Twitter user! (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
chadwhitacre committed Sep 13, 2012
1 parent 3755332 commit bf849a0
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 9 deletions.
95 changes: 95 additions & 0 deletions gittip/networks/twitter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import requests
from aspen import json, log, Response
from aspen.website import Website
from aspen.utils import typecheck
from gittip import db, networks


def upsert(user_info):
return networks.upsert( 'twitter'
, user_info['id']
, user_info['screen_name']
, user_info
)


def oauth_url(website, action, then=u""):
"""Given a website object and a string, return a URL string.
`action' is one of 'opt-in', 'lock' and 'unlock'
`then' is either a twitter username or an URL starting with '/'. It's
where we'll send the user after we get the redirect back from
GitHub.
"""
typecheck(website, Website, action, unicode, then, unicode)
assert action in [u'opt-in', u'lock', u'unlock']
url = u"https://twitter.com/login/oauth/authorize?consumer_key=%s&redirect_uri=%s"
url %= (website.twitter_consumer_key, website.twitter_callback)

# Pack action,then into data and base64-encode. Querystring isn't
# available because it's consumed by the initial GitHub request.

data = u'%s,%s' % (action, then)
data = data.encode('UTF-8').encode('base64').decode('US-ASCII')
url += u'?data=%s' % data
return url


def oauth_dance(website, qs):
"""Given a querystring, return a dict of user_info.
The querystring should be the querystring that we get from GitHub when
we send the user to the return value of oauth_url above.
See also:
http://developer.twitter.com/v3/oauth/
"""

log("Doing an OAuth dance with Github.")

if 'error' in qs:
raise Response(500, str(qs['error']))

data = { 'code': qs['code'].encode('US-ASCII')
, 'client_id': website.twitter_client_id
, 'client_secret': website.twitter_client_secret
}
r = requests.post("https://twitter.com/login/oauth/access_token", data=data)
assert r.status_code == 200, (r.status_code, r.text)

back = dict([pair.split('=') for pair in r.text.split('&')]) # XXX
if 'error' in back:
raise Response(400, back['error'].encode('utf-8'))
assert back.get('token_type', '') == 'bearer', back
access_token = back['access_token']

r = requests.get( "https://api.twitter.com/user"
, headers={'Authorization': 'token %s' % access_token}
)
assert r.status_code == 200, (r.status_code, r.text)
user_info = json.loads(r.text)
log("Done with OAuth dance with Github for %s (%s)."
% (user_info['login'], user_info['id']))

return user_info


def resolve(screen_name):
"""Given str, return a participant_id.
"""
FETCH = """\
SELECT participant_id
FROM social_network_users
WHERE network='twitter'
AND user_info -> 'screen_namec' = %s
""" # XXX Uniqueness constraint on screen_name?
rec = db.fetchone(FETCH, (screen_name,))
if rec is None:
raise Exception("Twitter user %s has no participant." % (screen_name))
return rec['participant_id']
2 changes: 1 addition & 1 deletion templates/participant.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ <h2>Your weekly {{ tip_or_pledge }} to {{ username }} is:</h2>

<div id="tips">
{% for amount in AMOUNTS %}
<button amount="{{ amount }}" tippee="{{ username }}"
<button amount="{{ amount }}" tippee="{{ participant_id }}"
class="tip{{ ' selected' if amount == my_tip else '' }}">${{ amount }}</button>
{% end %}
{% if my_tip not in AMOUNTS %}
Expand Down
10 changes: 3 additions & 7 deletions www/%participant_id/tip.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import decimal

from aspen import Response
from gittip import AMOUNTS, db
from gittip.networks import github


# ========================================================================== ^L
Expand All @@ -14,14 +13,11 @@ if not user.ANON:

# Get tipper and tippee.
# ======================
# XXX We could/should enforce that tips cannot be pledged at all to locked
# accounts.

tipper = user.id
if 'participant_id' in path: # gittip
tippee = path['participant_id']
elif 'login' in path: # github
tippee = github.resolve(path['login'])
else:
raise Response(400, "can't find tippee")
tippee = path['participant_id']


# Get and maybe set amount.
Expand Down
2 changes: 1 addition & 1 deletion www/on/github/%login/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
can_tip = False

tip_or_pledge = "pledge"
nbackers = get_number_of_backers(username)
nbackers = get_number_of_backers(participant_id)

# ========================================================================== ^L
{% extends templates/participant.html %}
Expand Down
118 changes: 118 additions & 0 deletions www/on/twitter/%screen_name/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
"""Twitter user page on Gittip.
"""
import decimal

import requests
from aspen import json, Response
from gittip import AMOUNTS, CARDINALS, db, get_tip, get_number_of_backers
from gittip.networks import twitter


# ========================================================================== ^L

# Try to load from Twitter.
# =========================

url = "https://api.twitter.com/1/users/show.json?screen_name=%s"
userinfo = requests.get(url % path['screen_name'])
if userinfo.status_code != 200:
raise Response(404)
userinfo = json.loads(userinfo.text)


# Try to load from Gittip.
# ========================

participant = False
username = userinfo['screen_name']
name = userinfo.get('name')
if not name:
name = username
userinfo['html_url'] = "https://twitter.com/%s" % username

participant_id, is_claimed, is_locked, balance = twitter.upsert(userinfo)
can_tip = not is_locked
lock_action = "unlock" if is_locked else "lock"
if is_claimed:
request.redirect('/%s/' % participant_id)

if not user.ANON:
my_tip = get_tip(user.id, participant_id)

tip_or_pledge = "pledge"
nbackers = get_number_of_backers(participant_id)

# ========================================================================== ^L
{% extends templates/participant.html %}

{% block their_voice %}
{% if is_locked %}

<h2 class="first"><b>{{ username }}</b> has opted out of Gittip.</h2>

<p>If you are <a href="{{ userinfo.get('html_url', '') }}">{{ username }}</a>
on Twitter, you can unlock your account to allow people to
pledge tips to you on Gittip. We never collect any money on your behalf
until you explicitly opt in.</p>

<a href="{{ twitter.oauth_url(website, u'unlock', username) }}"
><button>Unlock</button></a>

{% else %}
<script>
$(document).ready(Gittip.initTipButtons);
</script>

<h2 class="first"><b>{{ name }}</b> has not joined Gittip.</h2>

{% if user.ANON %}
<ul id="accounts">
<li>
<img src="{{ userinfo.get('avatar_url', '/assets/%s/no-avatar.png' % __version__) }}" />
Are you <a href="{{ userinfo['html_url'] }}">{{ userinfo['screen_name'] }}</a> from Twitter?<br />
<a href="{{ twitter.oauth_url(website, u'opt-in', username) }}">Click here</a> to opt in to Gittip.
</li>
</ul>
{% else %}
<ul id="accounts">
<li>
<img src="{{ userinfo.get('avatar_url', '/assets/%s/no-avatar.png' % __version__) }}" />
Are you <a href="{{ userinfo['html_url'] }}">{{ userinfo['screen_name'] }}</a> from Twitter?<br />
You&rsquo;ll have to <a href="/sign-out.html">sign out</a> and sign back in to claim this account.
</li>
</ul>
{% end %}


{% if nbackers == 0 %}
{% elif nbackers == 1 %}
<h3>There is one person ready to give.</h3>
{% elif nbackers < 10 %}
<h3>There are {{ CARDINALS[nbackers] }} people ready to give.</h3>
{% else %}
<h3>There are {{ nbackers }} people ready to give.</h3>
{% end %}


{% if not user.ANON %}

<p>{{ 'But we' if nbackers > 0 else 'We' }} will never collect money on
behalf of {{ username }} until they ask us to.</p>

{% else %}

<p>{{ 'But we' if nbackers > 0 else 'We' }} will never collect money on
your behalf until you ask us to.</p>

<h3>Don&rsquo;t like what you see?</h3>

<p>If you are {{ username }} you can explicitly opt out of Gittip by
locking this account. We don&rsquo;t allow new pledges to locked
accounts.</p>

<a href="{{ twitter.oauth_url(website, u'lock', username) }}"
><button>Lock</button></a>

{% end %}
{% end %}
{% end %}

0 comments on commit bf849a0

Please sign in to comment.