Commits

laiso committed 65a99fa

first commit

Comments (0)

Files changed (18)

+syntax: regexp
+(.*/)?\#[^/]*\#$
+(.*/)?\.\#[^/]*$
+
+syntax: glob
+*.pyc
+*.orig
+*.swp
+*.tmp
+*~
+*.pdf
+.DS_Store
+app/config_secret\.py
+var/*
+
+eggs/*
+bin/*
+app/lib/dist/*
+.installed.cfg
+application: pitweet-laiso-org
+version: 1
+runtime: python
+api_version: 1
+
+builtins:
+- appstats: on
+- datastore_admin: on
+- remote_api: on
+
+handlers:
+- url: /(robots\.txt|favicon\.ico)
+  static_files: static/\1
+  upload: static/(.*)
+
+- url: /static
+  static_dir: static
+
+- url: /_ah/queue/deferred
+  script: main.py
+  login: admin
+
+- url: /.*
+  script: main.py
+# -*- coding: utf-8 -*-
+"""App configuration."""
+from config_secret import *
+config = {}
+
+config['tipfy.sessions'] = {
+'secret_key': SESSION_SECRET_KEY,
+'default_backend': 'securecookie',
+'cookie_name':'session',
+'session_max_age': None,
+'cookie_args': {
+    'max_age':None,
+    'domain':None,
+    'path':'/',
+    'secure':None,
+    'httponly':False,
+    }
+}
+indexes:
+
+# AUTOGENERATED
+
+# This index.yaml is automatically updated whenever the dev_appserver
+# detects that a new type of query is run.  If you want to manage the
+# index.yaml file manually, remove the above marker line (the line
+# saying "# AUTOGENERATED").  If you want to manage some indexes
+# manually, move them above the marker line.  The index.yaml file is
+# automatically uploaded to the admin console when you next deploy
+# your application using appcfg.py.
+#!/usr/bin/env python
+
+"""
+A simple OAuth implementation for authenticating users with third party
+websites.
+
+A typical use case inside an AppEngine controller would be:
+
+1) Create the OAuth client. In this case we'll use the Twitter client,
+  but you could write other clients to connect to different services.
+
+  import oauth
+
+  consumer_key = "LKlkj83kaio2fjiudjd9...etc"
+  consumer_secret = "58kdujslkfojkjsjsdk...etc"
+  callback_url = "http://www.myurl.com/callback/twitter"
+
+  client = oauth.TwitterClient(consumer_key, consumer_secret, callback_url)
+
+2) Send the user to Twitter in order to login:
+
+  self.redirect(client.get_authorization_url())
+
+3) Once the user has arrived back at your callback URL, you'll want to
+  get the authenticated user information.
+
+  auth_token = self.request.get("oauth_token")
+  auth_verifier = self.request.get("oauth_verifier")
+  user_info = client.get_user_info(auth_token, auth_verifier=auth_verifier)
+
+  The "user_info" variable should then contain a dictionary of various
+  user information (id, picture url, etc). What you do with that data is up
+  to you.
+
+  That's it!
+
+4) If you need to, you can also call other other API URLs using
+  client.make_request() as long as you supply a valid API URL and an access
+  token and secret. Note, you may need to set method=urlfetch.POST.
+
+@author: Mike Knapp
+@copyright: Unrestricted. Feel free to use modify however you see fit. Please
+note however this software is unsupported. Please don't email me about it. :)
+"""
+
+from google.appengine.api import memcache
+from google.appengine.api import urlfetch
+from google.appengine.ext import db
+
+from cgi import parse_qs
+from django.utils import simplejson as json
+from hashlib import sha1
+from hmac import new as hmac
+from random import getrandbits
+from time import time
+from urllib import urlencode
+from urllib import quote as urlquote
+from urllib import unquote as urlunquote
+
+import logging
+
+
+TWITTER = "twitter"
+YAHOO = "yahoo"
+MYSPACE = "myspace"
+DROPBOX = "dropbox"
+LINKEDIN = "linkedin"
+
+
+class OAuthException(Exception):
+  pass
+
+
+def get_oauth_client(service, key, secret, callback_url):
+  """Get OAuth Client.
+
+  A factory that will return the appropriate OAuth client.
+  """
+
+  if service == TWITTER:
+    return TwitterClient(key, secret, callback_url)
+  elif service == YAHOO:
+    return YahooClient(key, secret, callback_url)
+  elif service == MYSPACE:
+    return MySpaceClient(key, secret, callback_url)
+  elif service == DROPBOX:
+    return DropboxClient(key, secret, callback_url)
+  elif service == LINKEDIN:
+    return LinkedInClient(key, secret, callback_url)
+  else:
+    raise Exception, "Unknown OAuth service %s" % service
+
+
+class AuthToken(db.Model):
+  """Auth Token.
+
+  A temporary auth token that we will use to authenticate a user with a
+  third party website. (We need to store the data while the user visits
+  the third party website to authenticate themselves.)
+
+  TODO: Implement a cron to clean out old tokens periodically.
+  """
+
+  service = db.StringProperty(required=True)
+  token = db.StringProperty(required=True)
+  secret = db.StringProperty(required=True)
+  created = db.DateTimeProperty(auto_now_add=True)
+
+
+class OAuthClient():
+
+  def __init__(self, service_name, consumer_key, consumer_secret, request_url,
+               access_url, callback_url=None):
+    """ Constructor."""
+
+    self.service_name = service_name
+    self.consumer_key = consumer_key
+    self.consumer_secret = consumer_secret
+    self.request_url = request_url
+    self.access_url = access_url
+    self.callback_url = callback_url
+
+  def prepare_request(self, url, token="", secret="", additional_params=None,
+                      method=urlfetch.GET, t=None, nonce=None):
+    """Prepare Request.
+
+    Prepares an authenticated request to any OAuth protected resource.
+
+    Returns the payload of the request.
+    """
+
+    def encode(text):
+      return urlquote(str(text), "~")
+
+    params = {
+      "oauth_consumer_key": self.consumer_key,
+      "oauth_signature_method": "HMAC-SHA1",
+      "oauth_timestamp": t if t else str(int(time())),
+      "oauth_nonce": nonce if nonce else str(getrandbits(64)),
+      "oauth_version": "1.0"
+    }
+
+    if token:
+      params["oauth_token"] = token
+    elif self.callback_url:
+      params["oauth_callback"] = self.callback_url
+
+    if additional_params:
+        params.update(additional_params)
+
+    for k,v in params.items():
+        if isinstance(v, unicode):
+            params[k] = v.encode('utf8')
+
+    # Join all of the params together.
+    params_str = "&".join(["%s=%s" % (encode(k), encode(params[k]))
+                           for k in sorted(params)])
+
+    # Join the entire message together per the OAuth specification.
+    message = "&".join(["GET" if method == urlfetch.GET else "POST",
+                       encode(url), encode(params_str)])
+
+    # Create a HMAC-SHA1 signature of the message.
+    key = "%s&%s" % (self.consumer_secret, secret) # Note compulsory "&".
+    signature = hmac(key, message, sha1)
+    digest_base64 = signature.digest().encode("base64").strip()
+    params["oauth_signature"] = digest_base64
+
+    # Construct the request payload and return it
+    return urlencode(params)
+
+  def make_async_request(self, url, token="", secret="", additional_params=None,
+                         protected=False, method=urlfetch.GET, headers={}):
+    """Make Request.
+
+    Make an authenticated request to any OAuth protected resource.
+
+    If protected is equal to True, the Authorization: OAuth header will be set.
+
+    A urlfetch response object is returned.
+    """
+
+    payload = self.prepare_request(url, token, secret, additional_params,
+                                   method)
+
+    if method == urlfetch.GET:
+      url = "%s?%s" % (url, payload)
+      payload = None
+
+    if protected:
+      headers["Authorization"] = "OAuth"
+
+    rpc = urlfetch.create_rpc(deadline=10.0)
+    urlfetch.make_fetch_call(rpc, url, method=method, headers=headers,
+                             payload=payload)
+    return rpc
+
+  def make_request(self, url, token="", secret="", additional_params=None,
+                   protected=False, method=urlfetch.GET, headers={}):
+
+    return self.make_async_request(url, token, secret, additional_params,
+                                   protected, method, headers).get_result()
+
+  def get_authorization_url(self):
+    """Get Authorization URL.
+
+    Returns a service specific URL which contains an auth token. The user
+    should be redirected to this URL so that they can give consent to be
+    logged in.
+    """
+
+    raise NotImplementedError, "Must be implemented by a subclass"
+
+  def get_user_info(self, auth_token, auth_verifier=""):
+    """Get User Info.
+
+    Exchanges the auth token for an access token and returns a dictionary
+    of information about the authenticated user.
+    """
+
+    auth_token = urlunquote(auth_token)
+    auth_verifier = urlunquote(auth_verifier)
+
+    auth_secret = memcache.get(self._get_memcache_auth_key(auth_token))
+
+    if not auth_secret:
+      result = AuthToken.gql("""
+        WHERE
+          service = :1 AND
+          token = :2
+        LIMIT
+          1
+      """, self.service_name, auth_token).get()
+
+      if not result:
+        logging.error("The auth token %s was not found in our db" % auth_token)
+        raise Exception, "Could not find Auth Token in database"
+      else:
+        auth_secret = result.secret
+
+    response = self.make_request(self.access_url,
+                                 token=auth_token,
+                                 secret=auth_secret,
+                                 additional_params={"oauth_verifier":
+                                                     auth_verifier})
+
+    # Extract the access token/secret from the response.
+    result = self._extract_credentials(response)
+
+    # Try to collect some information about this user from the service.
+    user_info = self._lookup_user_info(result["token"], result["secret"])
+    user_info.update(result)
+
+    return user_info
+
+  def _get_auth_token(self):
+    """Get Authorization Token.
+
+    Actually gets the authorization token and secret from the service. The
+    token and secret are stored in our database, and the auth token is
+    returned.
+    """
+
+    response = self.make_request(self.request_url)
+    result = self._extract_credentials(response)
+
+    auth_token = result["token"]
+    auth_secret = result["secret"]
+
+    # Save the auth token and secret in our database.
+    auth = AuthToken(service=self.service_name,
+                     token=auth_token,
+                     secret=auth_secret)
+    auth.put()
+
+    # Add the secret to memcache as well.
+    memcache.set(self._get_memcache_auth_key(auth_token), auth_secret,
+                 time=20*60)
+
+    return auth_token
+
+  def _get_memcache_auth_key(self, auth_token):
+
+    return "oauth_%s_%s" % (self.service_name, auth_token)
+
+  def _extract_credentials(self, result):
+    """Extract Credentials.
+
+    Returns an dictionary containing the token and secret (if present).
+    Throws an Exception otherwise.
+    """
+
+    token = None
+    secret = None
+    parsed_results = parse_qs(result.content)
+
+    if "oauth_token" in parsed_results:
+      token = parsed_results["oauth_token"][0]
+
+    if "oauth_token_secret" in parsed_results:
+      secret = parsed_results["oauth_token_secret"][0]
+
+    if not (token and secret) or result.status_code != 200:
+      logging.error("Could not extract token/secret: %s" % result.content)
+      raise OAuthException("Problem talking to the service")
+
+    return {
+      "service": self.service_name,
+      "token": token,
+      "secret": secret
+    }
+
+  def _lookup_user_info(self, access_token, access_secret):
+    """Lookup User Info.
+
+    Complies a dictionary describing the user. The user should be
+    authenticated at this point. Each different client should override
+    this method.
+    """
+
+    raise NotImplementedError, "Must be implemented by a subclass"
+
+  def _get_default_user_info(self):
+    """Get Default User Info.
+
+    Returns a blank array that can be used to populate generalized user
+    information.
+    """
+
+    return {
+      "id": "",
+      "username": "",
+      "name": "",
+      "picture": ""
+    }
+
+
+class TwitterClient(OAuthClient):
+  """Twitter Client.
+
+  A client for talking to the Twitter API using OAuth as the
+  authentication model.
+  """
+
+  def __init__(self, consumer_key, consumer_secret, callback_url):
+    """Constructor."""
+
+    OAuthClient.__init__(self,
+        TWITTER,
+        consumer_key,
+        consumer_secret,
+        "http://api.twitter.com/oauth/request_token",
+        "http://api.twitter.com/oauth/access_token",
+        callback_url)
+
+  def get_authorization_url(self):
+    """Get Authorization URL."""
+
+    token = self._get_auth_token()
+    return "http://api.twitter.com/oauth/authorize?oauth_token=%s" % token
+
+  def get_authenticate_url(self):
+    """Get Authentication URL."""
+    token = self._get_auth_token()
+    return "http://api.twitter.com/oauth/authenticate?oauth_token=%s" % token
+
+  def _lookup_user_info(self, access_token, access_secret):
+    """Lookup User Info.
+
+    Lookup the user on Twitter.
+    """
+
+    response = self.make_request(
+        "http://api.twitter.com/account/verify_credentials.json",
+        token=access_token, secret=access_secret, protected=True)
+
+    data = json.loads(response.content)
+
+    user_info = self._get_default_user_info()
+    user_info["id"] = data["id"]
+    user_info["username"] = data["screen_name"]
+    user_info["name"] = data["name"]
+    user_info["picture"] = data["profile_image_url"]
+
+    return user_info
+
+
+class MySpaceClient(OAuthClient):
+  """MySpace Client.
+
+  A client for talking to the MySpace API using OAuth as the
+  authentication model.
+  """
+
+  def __init__(self, consumer_key, consumer_secret, callback_url):
+    """Constructor."""
+
+    OAuthClient.__init__(self,
+        MYSPACE,
+        consumer_key,
+        consumer_secret,
+        "http://api.myspace.com/request_token",
+        "http://api.myspace.com/access_token",
+        callback_url)
+
+  def get_authorization_url(self):
+    """Get Authorization URL."""
+
+    token = self._get_auth_token()
+    return ("http://api.myspace.com/authorize?oauth_token=%s"
+            "&oauth_callback=%s" % (token, urlquote(self.callback_url)))
+
+  def _lookup_user_info(self, access_token, access_secret):
+    """Lookup User Info.
+
+    Lookup the user on MySpace.
+    """
+
+    response = self.make_request("http://api.myspace.com/v1/user.json",
+        token=access_token, secret=access_secret, protected=True)
+
+    data = json.loads(response.content)
+
+    user_info = self._get_default_user_info()
+    user_info["id"] = data["userId"]
+    username = data["webUri"].replace("http://www.myspace.com/", "")
+    user_info["username"] = username
+    user_info["name"] = data["name"]
+    user_info["picture"] = data["image"]
+
+    return user_info
+
+
+class YahooClient(OAuthClient):
+  """Yahoo! Client.
+
+  A client for talking to the Yahoo! API using OAuth as the
+  authentication model.
+  """
+
+  def __init__(self, consumer_key, consumer_secret, callback_url):
+    """Constructor."""
+
+    OAuthClient.__init__(self,
+        YAHOO,
+        consumer_key,
+        consumer_secret,
+        "https://api.login.yahoo.com/oauth/v2/get_request_token",
+        "https://api.login.yahoo.com/oauth/v2/get_token",
+        callback_url)
+
+  def get_authorization_url(self):
+    """Get Authorization URL."""
+
+    token = self._get_auth_token()
+    return ("https://api.login.yahoo.com/oauth/v2/request_auth?oauth_token=%s"
+            % token)
+
+  def _lookup_user_info(self, access_token, access_secret):
+    """Lookup User Info.
+
+    Lookup the user on Yahoo!
+    """
+
+    user_info = self._get_default_user_info()
+
+    # 1) Obtain the user's GUID.
+    response = self.make_request(
+        "http://social.yahooapis.com/v1/me/guid", token=access_token,
+        secret=access_secret, additional_params={"format": "json"},
+        protected=True)
+
+    data = json.loads(response.content)["guid"]
+    guid = data["value"]
+
+    # 2) Inspect the user's profile.
+    response = self.make_request(
+        "http://social.yahooapis.com/v1/user/%s/profile/usercard" % guid,
+         token=access_token, secret=access_secret,
+         additional_params={"format": "json"}, protected=True)
+
+    data = json.loads(response.content)["profile"]
+
+    user_info["id"] = guid
+    user_info["username"] = data["nickname"].lower()
+    user_info["name"] = data["nickname"]
+    user_info["picture"] = data["image"]["imageUrl"]
+
+    return user_info
+
+
+class DropboxClient(OAuthClient):
+  """Dropbox Client.
+
+  A client for talking to the Dropbox API using OAuth as the authentication
+  model.
+  """
+
+  def __init__(self, consumer_key, consumer_secret, callback_url):
+    """Constructor."""
+
+    OAuthClient.__init__(self,
+        DROPBOX,
+        consumer_key,
+        consumer_secret,
+        "https://api.dropbox.com/0/oauth/request_token",
+        "https://api.dropbox.com/0/oauth/access_token",
+        callback_url)
+
+  def get_authorization_url(self):
+    """Get Authorization URL."""
+
+    token = self._get_auth_token()
+    return ("http://www.dropbox.com/0/oauth/authorize?"
+            "oauth_token=%s&oauth_callback=%s" % (token,
+                                                  urlquote(self.callback_url)))
+
+  def _lookup_user_info(self, access_token, access_secret):
+    """Lookup User Info.
+
+    Lookup the user on Dropbox.
+    """
+
+    response = self.make_request("http://api.dropbox.com/0/account/info",
+                                 token=access_token, secret=access_secret,
+                                 protected=True)
+
+    data = json.loads(response.content)
+    user_info = self._get_default_user_info()
+    user_info["id"] = data["uid"]
+    user_info["name"] = data["display_name"]
+    user_info["country"] = data["country"]
+
+    return user_info
+
+
+class LinkedInClient(OAuthClient):
+  """LinkedIn Client.
+
+  A client for talking to the LinkedIn API using OAuth as the
+  authentication model.
+  """
+
+  def __init__(self, consumer_key, consumer_secret, callback_url):
+    """Constructor."""
+
+    OAuthClient.__init__(self,
+        LINKEDIN,
+        consumer_key,
+        consumer_secret,
+        "https://api.linkedin.com/uas/oauth/requestToken",
+        "https://api.linkedin.com/uas/oauth/accessToken",
+        callback_url)
+
+  def get_authorization_url(self):
+    """Get Authorization URL."""
+
+    token = self._get_auth_token()
+    return ("https://www.linkedin.com/uas/oauth/authenticate?oauth_token=%s"
+            "&oauth_callback=%s" % (token, urlquote(self.callback_url)))
+
+  def _lookup_user_info(self, access_token, access_secret):
+    """Lookup User Info.
+
+    Lookup the user on LinkedIn
+    """
+
+    user_info = self._get_default_user_info()
+
+    # Grab the user's profile from LinkedIn.
+    response = self.make_request("http://api.linkedin.com/v1/people/~:"
+                                 "(picture-url,id,first-name,last-name)",
+                                 token=access_token,
+                                 secret=access_secret,
+                                 protected=False,
+                                 headers={"x-li-format":"json"})
+
+    data = json.loads(response.content)
+    user_info = self._get_default_user_info()
+    user_info["id"] = data["id"]
+    user_info["picture"] = data["pictureUrl"]
+    user_info["name"] = data["firstName"] + " " + data["lastName"]
+    return user_info
+# -*- coding: utf-8 -*-
+"""WSGI app setup."""
+import os
+import sys
+
+if 'lib' not in sys.path:
+    # Add lib as primary libraries directory, with fallback to lib/dist
+    # and optionally to lib/dist.zip, loaded using zipimport.
+    sys.path[0:0] = ['lib', 'lib/dist', 'lib/dist.zip']
+
+from tipfy import Tipfy
+from config import config
+from urls import rules
+
+
+def enable_appstats(app):
+    """Enables appstats middleware."""
+    if debug:
+        return
+
+    from google.appengine.ext.appstats.recording import appstats_wsgi_middleware
+    app.wsgi_app = appstats_wsgi_middleware(app.wsgi_app)
+
+
+def enable_jinja2_debugging():
+    """Enables blacklisted modules that help Jinja2 debugging."""
+    if not debug:
+        return
+
+    # This enables better debugging info for errors in Jinja2 templates.
+    from google.appengine.tools.dev_appserver import HardenedModulesHook
+    HardenedModulesHook._WHITE_LIST_C_MODULES += ['_ctypes', 'gestalt']
+
+
+# Is this the development server?
+debug = os.environ.get('SERVER_SOFTWARE', '').startswith('Dev')
+
+# Instantiate the application.
+app = Tipfy(rules=rules, config=config, debug=debug)
+enable_appstats(app)
+enable_jinja2_debugging()
+
+
+def main():
+    # Run the app.
+    app.run()
+
+
+if __name__ == '__main__':
+    main()

app/pitweet/__init__.py

+

app/pitweet/handlers.py

+# -*- coding:utf-8 -*-
+"""
+    hello_world.handlers
+    ~~~~~~~~~~~~~~~~~~~~
+
+    Hello, World!: the simplest tipfy app.
+
+    :copyright: 2009 by tipfy.org.
+    :license: BSD, see LICENSE for more details.
+"""
+
+import logging
+import random
+import urllib, urllib2
+import pickle
+
+from django.utils import simplejson as json
+from tipfy import RequestHandler, Response
+from tipfy.sessions import SessionMiddleware
+from tipfyext.jinja2 import Jinja2Mixin
+from google.appengine.api import memcache, urlfetch
+from google.appengine.ext import webapp
+from google.appengine.ext.webapp import util
+
+import oauth
+import tweepy
+
+from config_secret import CONSUMER_KEY, CONSUMER_SECRET
+
+class CallbackHandler(RequestHandler):
+    middleware = [SessionMiddleware()]
+    def get(self):
+        cb = self.request.host_url + 'callback'
+        client = oauth.get_oauth_client(oauth.TWITTER, \
+                CONSUMER_KEY, CONSUMER_SECRET, cb)
+        auth_token = self.request.args.get("oauth_token")
+        auth_verifier = self.request.args.get("oauth_verifier")
+        user_info = client.get_user_info(auth_token, auth_verifier=auth_verifier)
+        self.session['userinfo'] = user_info
+        return self.redirect('/')
+
+class MainHandler(RequestHandler, Jinja2Mixin):
+    middleware = [SessionMiddleware()]
+    def get(self):
+        user_info = self.session.get('userinfo')
+        ctx = {
+            'user_info': user_info,
+            'context': None,
+        }
+        return self.render_response('index.html', **ctx)
+
+class LogoutHandler(RequestHandler):
+    middleware = [SessionMiddleware()]
+    def get(self):
+        self.session.clear()
+        return self.redirect('/')
+
+class LoginHandler(RequestHandler):
+    def get(self):
+        cb = self.request.host_url + 'callback'
+        client = oauth.get_oauth_client(oauth.TWITTER, \
+                CONSUMER_KEY, CONSUMER_SECRET, cb)
+        return self.redirect(client.get_authorization_url())
+
+class TweetHandler(MainHandler):
+
+    def image_urls(self, query):
+        query = query.encode('utf-8')
+        url = "http://www.google.com/uds/GimageSearch?%s&lang=ja&v=1.0" % \
+                urllib.urlencode(dict(q=query))
+        json_resp = urllib2.urlopen(url).read()
+        return [u.get("url").encode("ascii") for u in json.loads(json_resp).get("responseData").get("results")]
+
+    def image_context(self, urls):
+        url = urls.pop()
+        if url:
+            try:
+                resp = urllib2.urlopen(url)
+                if resp.code == 200:
+                    info = resp.info()
+                    logging.debug(url)
+                    body = resp.read()
+                    logging.debug('body: %d bytes.' % len(body))
+                    context = dict(url=url, 
+                        store=False, 
+                        body=body, 
+                        file_type='image/*',
+                        filename=resp.url.rsplit('/', 1)[-1])
+                    memcache.add(url, pickle.dumps(context), 60*60*24)
+                    return context
+                else:
+                    return self.image_context(urls)
+            except urllib2.HTTPError, e:
+                return self.image_context(urls)
+
+    def post(self):
+        user_info = self.session.get('userinfo')
+        ctx = {
+            'user_info': user_info,
+            'context': {},
+            'text': '',
+        }
+        cb = self.request.host_url + 'callback'
+        client = oauth.get_oauth_client(oauth.TWITTER, \
+                CONSUMER_KEY, CONSUMER_SECRET, cb)
+        user_info = self.session.get('userinfo')
+        text = self.request.values.get('tweet')
+        ctx['text'] = text or ''
+
+        url = self.request.values.get('url')
+        if url:
+            ctx['context'] = memcache.get(url)
+        if not ctx['context']:
+            urls = self.image_urls(text)
+            if len(urls):
+                random.shuffle(urls)
+                content = self.image_context(urls)
+                assert content['url'] 
+                ctx['context']['url'] = content['url']
+        else:
+            ctx['context'] = pickle.loads(ctx['context'])
+            ctx['context']['store'] = True
+            auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
+            auth.set_access_token(user_info.get('token'), user_info.get('secret'))
+            api = tweepy.API(auth)
+            user = api.update_profile_image(ctx['context'])
+            assert user
+            assert text
+            status = api.update_status(text)
+            assert status
+        return self.render_response('index.html', **ctx)
+
+
+
+

app/static/favicon.ico

Added
New image

app/static/robots.txt

+

app/templates/index.html

+<html lang="ja">
+<head>
+    <meta charset="utf-8">
+    <title>投稿するテキストで画像検索してアイコン変えるやつ</title>
+</head>
+<body>
+{% if user_info %}
+
+<a href='http://twitter.com/{{ user_info.username }}'>@{{ user_info.username }}</a>
+<form action="/tweet" method="POST">
+    {% if context %}
+    <input type="hidden" name="url" value="{{ context.url }}">
+    {% else %}
+    <input name="tweet" length="10" value="{{ text }}">
+    <input type="submit" value="プレビュー">
+    {% endif %}
+    {% if context.store %}
+    <p>アイコンを <img src='{{ context.url }}' width='128'> にした</p>
+    {% elif context.url %}
+    <input name="tweet" length="10" value="{{ text }}">
+    <p>アイコンを <img src='{{ context.url }}' width='128'> にしてつぶやく?</p>
+    <input type="submit" value="OK">
+    {% endif %}
+</form>
+    <a href="/">もどる</a>
+
+<a href='/logout'>LOGOUT</a>
+{% else %}
+<a href='/login'>LOGIN</a>
+{% endif %}
+
+</body>
+</html>
+# -*- coding: utf-8 -*-
+"""URL definitions."""
+from tipfy import Rule
+
+rules = [
+    Rule('/', name='index', handler='pitweet.handlers.MainHandler'),
+    Rule('/login', name='login', handler='pitweet.handlers.LoginHandler'),
+    Rule('/logout', name='logout', handler='pitweet.handlers.LogoutHandler'),
+    Rule('/callback', name='callback', handler='pitweet.handlers.CallbackHandler'),
+    Rule('/tweet', name='tweet', handler='pitweet.handlers.TweetHandler'),
+]
+# Extraction from Python source files
+[python: **.py]
+# Extraction from Jinja2 HTML templates
+[jinja2: **/**.html]
+encoding = utf-8
+line_statement_prefix = %
+
+[extractors]
+jinja2 = jinja2.ext:babel_extract
+##############################################################################
+#
+# Copyright (c) 2006 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+"""
+
+import os, shutil, sys, tempfile, textwrap, urllib, urllib2, subprocess
+from optparse import OptionParser
+
+if sys.platform == 'win32':
+    def quote(c):
+        if ' ' in c:
+            return '"%s"' % c # work around spawn lamosity on windows
+        else:
+            return c
+else:
+    quote = str
+
+# See zc.buildout.easy_install._has_broken_dash_S for motivation and comments.
+stdout, stderr = subprocess.Popen(
+    [sys.executable, '-Sc',
+     'try:\n'
+     '    import ConfigParser\n'
+     'except ImportError:\n'
+     '    print 1\n'
+     'else:\n'
+     '    print 0\n'],
+    stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
+has_broken_dash_S = bool(int(stdout.strip()))
+
+# In order to be more robust in the face of system Pythons, we want to
+# run without site-packages loaded.  This is somewhat tricky, in
+# particular because Python 2.6's distutils imports site, so starting
+# with the -S flag is not sufficient.  However, we'll start with that:
+if not has_broken_dash_S and 'site' in sys.modules:
+    # We will restart with python -S.
+    args = sys.argv[:]
+    args[0:0] = [sys.executable, '-S']
+    args = map(quote, args)
+    os.execv(sys.executable, args)
+# Now we are running with -S.  We'll get the clean sys.path, import site
+# because distutils will do it later, and then reset the path and clean
+# out any namespace packages from site-packages that might have been
+# loaded by .pth files.
+clean_path = sys.path[:]
+import site
+sys.path[:] = clean_path
+for k, v in sys.modules.items():
+    if k in ('setuptools', 'pkg_resources') or (
+        hasattr(v, '__path__') and
+        len(v.__path__)==1 and
+        not os.path.exists(os.path.join(v.__path__[0],'__init__.py'))):
+        # This is a namespace package.  Remove it.
+        sys.modules.pop(k)
+
+is_jython = sys.platform.startswith('java')
+
+setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py'
+distribute_source = 'http://python-distribute.org/distribute_setup.py'
+
+# parsing arguments
+def normalize_to_url(option, opt_str, value, parser):
+    if value:
+        if '://' not in value: # It doesn't smell like a URL.
+            value = 'file://%s' % (
+                urllib.pathname2url(
+                    os.path.abspath(os.path.expanduser(value))),)
+        if opt_str == '--download-base' and not value.endswith('/'):
+            # Download base needs a trailing slash to make the world happy.
+            value += '/'
+    else:
+        value = None
+    name = opt_str[2:].replace('-', '_')
+    setattr(parser.values, name, value)
+
+usage = '''\
+[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options]
+
+Bootstraps a buildout-based project.
+
+Simply run this script in a directory containing a buildout.cfg, using the
+Python that you want bin/buildout to use.
+
+Note that by using --setup-source and --download-base to point to
+local resources, you can keep this script from going over the network.
+'''
+
+parser = OptionParser(usage=usage)
+parser.add_option("-v", "--version", dest="version",
+                          help="use a specific zc.buildout version")
+parser.add_option("-d", "--distribute",
+                   action="store_true", dest="use_distribute", default=True,
+                   help="Use Distribute rather than Setuptools.")
+parser.add_option("--setup-source", action="callback", dest="setup_source",
+                  callback=normalize_to_url, nargs=1, type="string",
+                  help=("Specify a URL or file location for the setup file. "
+                        "If you use Setuptools, this will default to " +
+                        setuptools_source + "; if you use Distribute, this "
+                        "will default to " + distribute_source +"."))
+parser.add_option("--download-base", action="callback", dest="download_base",
+                  callback=normalize_to_url, nargs=1, type="string",
+                  help=("Specify a URL or directory for downloading "
+                        "zc.buildout and either Setuptools or Distribute. "
+                        "Defaults to PyPI."))
+parser.add_option("--eggs",
+                  help=("Specify a directory for storing eggs.  Defaults to "
+                        "a temporary directory that is deleted when the "
+                        "bootstrap script completes."))
+parser.add_option("-t", "--accept-buildout-test-releases",
+                  dest='accept_buildout_test_releases',
+                  action="store_true", default=False,
+                  help=("Normally, if you do not specify a --version, the "
+                        "bootstrap script and buildout gets the newest "
+                        "*final* versions of zc.buildout and its recipes and "
+                        "extensions for you.  If you use this flag, "
+                        "bootstrap and buildout will get the newest releases "
+                        "even if they are alphas or betas."))
+parser.add_option("-c", None, action="store", dest="config_file",
+                   help=("Specify the path to the buildout configuration "
+                         "file to be used."))
+
+options, args = parser.parse_args()
+
+# if -c was provided, we push it back into args for buildout's main function
+if options.config_file is not None:
+    args += ['-c', options.config_file]
+
+if options.eggs:
+    eggs_dir = os.path.abspath(os.path.expanduser(options.eggs))
+else:
+    eggs_dir = tempfile.mkdtemp()
+
+if options.setup_source is None:
+    if options.use_distribute:
+        options.setup_source = distribute_source
+    else:
+        options.setup_source = setuptools_source
+
+if options.accept_buildout_test_releases:
+    args.append('buildout:accept-buildout-test-releases=true')
+args.append('bootstrap')
+
+try:
+    import pkg_resources
+    import setuptools # A flag.  Sometimes pkg_resources is installed alone.
+    if not hasattr(pkg_resources, '_distribute'):
+        raise ImportError
+except ImportError:
+    ez_code = urllib2.urlopen(
+        options.setup_source).read().replace('\r\n', '\n')
+    ez = {}
+    exec ez_code in ez
+    setup_args = dict(to_dir=eggs_dir, download_delay=0)
+    if options.download_base:
+        setup_args['download_base'] = options.download_base
+    if options.use_distribute:
+        setup_args['no_fake'] = True
+    ez['use_setuptools'](**setup_args)
+    if 'pkg_resources' in sys.modules:
+        reload(sys.modules['pkg_resources'])
+    import pkg_resources
+    # This does not (always?) update the default working set.  We will
+    # do it.
+    for path in sys.path:
+        if path not in pkg_resources.working_set.entries:
+            pkg_resources.working_set.add_entry(path)
+
+cmd = [quote(sys.executable),
+       '-c',
+       quote('from setuptools.command.easy_install import main; main()'),
+       '-mqNxd',
+       quote(eggs_dir)]
+
+if not has_broken_dash_S:
+    cmd.insert(1, '-S')
+
+find_links = options.download_base
+if not find_links:
+    find_links = os.environ.get('bootstrap-testing-find-links')
+if find_links:
+    cmd.extend(['-f', quote(find_links)])
+
+if options.use_distribute:
+    setup_requirement = 'distribute'
+else:
+    setup_requirement = 'setuptools'
+ws = pkg_resources.working_set
+setup_requirement_path = ws.find(
+    pkg_resources.Requirement.parse(setup_requirement)).location
+env = dict(
+    os.environ,
+    PYTHONPATH=setup_requirement_path)
+
+requirement = 'zc.buildout'
+version = options.version
+if version is None and not options.accept_buildout_test_releases:
+    # Figure out the most recent final version of zc.buildout.
+    import setuptools.package_index
+    _final_parts = '*final-', '*final'
+    def _final_version(parsed_version):
+        for part in parsed_version:
+            if (part[:1] == '*') and (part not in _final_parts):
+                return False
+        return True
+    index = setuptools.package_index.PackageIndex(
+        search_path=[setup_requirement_path])
+    if find_links:
+        index.add_find_links((find_links,))
+    req = pkg_resources.Requirement.parse(requirement)
+    if index.obtain(req) is not None:
+        best = []
+        bestv = None
+        for dist in index[req.project_name]:
+            distv = dist.parsed_version
+            if _final_version(distv):
+                if bestv is None or distv > bestv:
+                    best = [dist]
+                    bestv = distv
+                elif distv == bestv:
+                    best.append(dist)
+        if best:
+            best.sort()
+            version = best[-1].version
+if version:
+    requirement = '=='.join((requirement, version))
+cmd.append(requirement)
+
+if is_jython:
+    import subprocess
+    exitcode = subprocess.Popen(cmd, env=env).wait()
+else: # Windows prefers this, apparently; otherwise we would prefer subprocess
+    exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env]))
+if exitcode != 0:
+    sys.stdout.flush()
+    sys.stderr.flush()
+    print ("An error occurred when trying to install zc.buildout. "
+           "Look above this message for any errors that "
+           "were output by easy_install.")
+    sys.exit(exitcode)
+
+ws.add_entry(eggs_dir)
+ws.require(requirement)
+import zc.buildout.buildout
+zc.buildout.buildout.main(args)
+if not options.eggs: # clean up temporary egg directory
+    shutil.rmtree(eggs_dir)
+[buildout]
+parts =
+#    gae_sdk
+    gae_tools
+    app_lib
+
+# Generate relative paths for eggs so that the buildout can be moved around.
+relative-paths = true
+
+# Unzip eggs automatically, if needed.
+unzip = true
+
+# Define versions for installed packages.
+extends = versions.cfg
+versions = versions
+
+# Enable this to save all picked versions in the versions.cfg file.
+# extensions = buildout.dumppickedversions
+# dump-picked-versions-file = versions.cfg
+
+# Keep internal stuff in a subdirectory.
+download-cache = var/downloads
+# Buildout bug: it doesn't honor custom egg dir this in parts/buildout/site.py
+# Until it is fixed we need to use the standard eggs dir.
+# eggs-directory = var/eggs
+develop-eggs-directory = var/develop-eggs
+parts-directory = var/parts
+
+[gae_sdk]
+# Dowloads and extracts the App Engine SDK.
+recipe = appfy.recipe.gae:sdk
+url = http://googleappengine.googlecode.com/files/google_appengine_1.4.2.zip
+
+[gae_tools]
+# Installs appcfg, bulkload_client, bulkloader, dev_appserver, remote_api_shell
+# and python executables in the bin directory.
+recipe = appfy.recipe.gae:tools
+# Add these paths to sys.path in the generated scripts.
+extra-paths =
+    app
+    app/lib
+    app/lib/dist
+
+[app_lib]
+# Sets the library dependencies for the app.
+recipe = appfy.recipe.gae:app_lib
+lib-directory = app/lib/dist
+use-zipimport = false
+
+# Define the packages to download. Only tipfy is included, but you can add
+# others or uncomment the extra lines to add those common packages.
+eggs =
+    tipfy-dev
+#    babel
+#    gaepytz
+    werkzeug
+    jinja2
+    wtforms
+
+# Don't copy files that match these glob patterns.
+ignore-globs =
+    *.c
+    *.pyc
+    *.pyo
+    *.so
+    */test
+    */tests
+    */testsuite
+    */django
+    */sqlalchemy
+
+# Don't install these packages or modules.
+ignore-packages =
+    distribute
+    setuptools
+    easy_install
+    site
+    ssl
+    pkg_resources
+[dev_appserver]
+# Set default values to start the dev_appserver. All options from the command
+# line are allowed, one per line. If an option is provided when calling
+# dev_appserver, it will override the default value if it is set. Values are
+# used as they are; don't use variables here.
+defaults =
+    --datastore_path=var/data.store
+    --history_path=var/history.store
+    --blobstore_path=var/blob.store
+    app
+[versions]