Google App Engine の URL Fetch API で Twitter

Google App Engine ではソケットが使えないが、そのかわりに URL Fetch API が用意されている。それで TwitterAPI をたたいてみる。

作ってみる

django-admin でプロジェクトとアプリケーションを作る。

~/letter/gae $ mkdir twi
~/letter/gae $ cd twi
~/letter/gae/twi $ django-admin startproject twipro
~/letter/gae/twi $ cd twipro
~/letter/gae/twi/twipro $ django-admin startapp twi

普通の app.yamlDjango 用の main.py を作成。

settings.py を編集。

(前略)
#ROOT_URLCONF = 'twipro.urls'
ROOT_URLCONF = 'urls'
(後略)

そろそろ dev_appserver を動かしておく。

~/letter/gae/twipro $ python2.5 ~/local/opt/google_appengine/dev_appserver.py ~/letter/gae/twipro/

http://localhost:8080/ にアクセスすると "It worked!" が表示された。

中略

なんかごちゃごちゃやってたけど忘れた。娘のバレエ教室についていってたもので。

URL Fetch APITwitter API をたたいてみる

views.py

# -*- coding: utf-8 -*-
import base64, urllib

from xml.etree import ElementTree
from django.http import HttpResponse
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from google.appengine.api import urlfetch
from google.appengine.api import users
from twipro.twi.models import Account
from twipro.twi.models import TwitterData

# フレンドライン
def friends(request):
    user = users.get_current_user()
    if not user:
        return HttpResponseRedirect(users.create_login_url("/"))
    account = get_account()
    if not account:
        return render_to_response('register.html')
    friends_line = get_friends_line(account)
    make_data(friends_line)
    data = {
        'data': TwitterData.all().filter("user =", user).order("-created_at")
        }
    return render_to_response('friends-line.html', data)

def get_account():
    return Account.all().filter("user =", users.get_current_user()).get()

# フレンドラインを取得する
def get_friends_line(account):
    url = "http://twitter.com/statuses/friends_timeline/%s.xml" % \
        account.twitter_id
    result = urlfetch.fetch(url)
    if result.status_code == 200:
        return result.content
    else:
        return "error"

# Twitter の ID 登録
def register(request):
    user = users.get_current_user()
    account = Account(user = user,
                      twitter_id = request.POST['id'],
                      twitter_passwd = request.POST['passwd'])
    account.put()
    return HttpResponseRedirect("/")

# データストアに登録
def make_data(friends_line):
    etree = ElementTree.fromstring(friends_line)
    for each in etree.findall('./status'):
        id = key_name = each.findtext('id')
        if TwitterData.all().filter("id =", id).get() is None:
            data = TwitterData(
                id = id,
                user = users.get_current_user(),
                screen_name =  each.findtext('user/screen_name'),
                text =  each.findtext('text'),
                in_reply_to =  each.findtext('in_reply_to'),
                in_reply_to_user_id =  each.findtext('in_reply_to_user_id'),
                created_at = each.findtext('created_at'))
            data.put()

# 更新
def update(request):
    account = get_account()
    status = request.POST['status']
    url = 'http://twitter.com/statuses/update.xml'
    result = urlfetch.fetch(
        url,
        urllib.urlencode({'status': status }),
        urlfetch.POST,
        make_request_header(account))
    return HttpResponseRedirect("/")

# 基本認証のためのヘッダ
def make_request_header(account):
    return { "Authorization": "Basic " + \
                 base64.b64encode("%s:%s" % (account.twitter_id,
                                             account.twitter_passwd)) }

こんなに view.py にごちゃごちゃ書いていいのか?

medels.py

# -*- coding: utf-8 -*-
import datetime, time, re

from google.appengine.ext import db

class Account(db.Model):
    user = db.UserProperty()
    twitter_id = db.StringProperty(required=True)
    twitter_passwd = db.StringProperty()

class TwitterData(db.Model):
    id = db.StringProperty()
    user = db.UserProperty()
    screen_name = db.StringProperty()
    text = db.StringProperty(multiline=True)
    in_reply_to = db.StringProperty()
    in_reply_to_user_id = db.StringProperty()
    created_at = db.StringProperty()

    def ago(self):
        dt = datetime.datetime(*time.strptime(
                self.created_at, "%a %b %d %H:%M:%S +0000 %Y")[0:5])
        now = datetime.datetime.utcnow()
        delta = now - dt
        s = ""
        if delta.days != 0:
            return "%d日前" % delta.days
        if delta.seconds >= 60:
            return "%d分前" % (delta.seconds / 60)
        return "%d秒前" % delta.seconds

    def texted(self):
        return re.sub(
            r"(https?://[^ ]+)",
            r"<a href='\1' target='_blank'>\1</a>", 
            self.text)

Twitter のパスワードも持っちゃうのはどうなん?

friends-line.html

{% extends 'base.html' %}
{% block main %}
<h1>フレンドライン</h1>
<form action="/update/" method="post">
    <input typu="text" name="status" size="80">
</form>
<table class="line">
{% for each in data %}
    <tr>
      <td>{{each.in_reply_to_user_id}}</td>
      <td nowrap>{{each.screen_name}}</td>
      <td>{{each.texted}}</td>
      <td nowrap>{{each.ago}}</td>
    </tr>
{% endfor %}
</table>
{% endblock %}

base.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <link href="/static/default.css" type="text/css" rel="stylesheet"></link>
    <title>twi</title>
  </head>
  <body>
    {% block main %}
    {% endblock %}
  </body>
</html>

雑感

filetr("user =", ...) の = の前のスペースは必須。
end とかがないのでソースの形が Lisp っぽい。
urlfetch.fetch で簡単に Twitter API はたたけたけど、どうせなら既存の python-twitter とかを使いたい。Common Lisp の Gray Stream みたな感じで python-twitter の下で人知れず urlfetch.fetch が動くみたいなことができないかな。