Pusher + Google App EngineでリアルタイムWeb

「WebSocketで目指せ“リアルタイムWeb”!」(http://www.atmarkit.co.jp/fcoding/articles/websocket/02/websocket02b.html)
これ読んで面白そうだと思ったので、とりあえずTwitterのpublic timelineを表示していくWebサイトを作ってみるよ。

Pusherに登録

http://pusherapp.com/
さくっと登録しましょう。
DashboardApi accessapi_id, key, secretが入手できる。

コーディング

サーバーとしてGAEを使用する。Pusherのアーキテクチャとかは上の記事を読んでください。
HTML

<!DOCTYPE html>
<head>
  <title>Pusher Test</title>
  <link href="/css/main.css" media="screen" rel="stylesheet" type="text/css" /> 
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
  <script type="text/javascript" src="http://js.pusherapp.com/1.6/pusher.js" ></script>
  <script type="text/javascript" src="/js/main.js" ></script>
  <body>
	<div id="container">
	</div>
  </body>
</head>

Javascript

function generate(data){
	var tweet = '<div class="tweet"><img src="' + data.profile_image_url + '" width="50px" height="50px" alt="photo"/>'
				+ '<a href="http://twitter.com/' + data.screen_name +'">' + data.screen_name + '</a></div>';
	tweet = $(tweet).css({
			'background' : 'white',
			'border' : '2px solid #999', 
			'padding' : '5px',
			'margin-bottom' : '1px',
			'-webkit-border-radius' : '10px',
			'-moz-border-radius' : '10px'
			});
			
	var text = '<div class="text">' + data.text + '</div>'
	text = $(text).css({
			'display' : 'inline',
			'margin-left' : '10px'
		});
	
	tweet.append(text);
	
	$('#container').prepend(tweet);
	
	if ($('.tweet').length >= 15) {
		$('.tweet:last').remove();
	}
}

$(document).ready(function(){
	var pusher = new Pusher('key');
	pusher.subscribe('test_channel');
	pusher.bind('note-update', function(data) {
	  generate(data);
	});	
});

Python(GAE)
pusherのライブラリとしてpusherとかgea-pusherがあるよ。
今回はpusherを使用。

# -*- coding: utf-8 -*-
from google.appengine.ext import webapp
from google.appengine.ext import db
from google.appengine.ext.webapp import template
from google.appengine.ext.webapp.util import run_wsgi_app
import tweepy
from pusher import Pusher, Channel
import os

class Tweet(db.Model):
  id = db.IntegerProperty(required = True)

class Main(webapp.RequestHandler):
  def get(self):
    path = os.path.join(os.path.dirname(__file__), 'main.html')
    self.response.out.write(template.render(path, {}))
    
class Trigger(webapp.RequestHandler):
  def get(self):
    # 送信済みTweetのid
    tweeted = [tweet.id for tweet in Tweet.all().fetch(limit = 1000)]

    app_id = 'app_id'
    key = 'key'
    secret = 'secret'
    pusher = Pusher(app_id, key, secret)

    name = 'test_channel'
    channel = Channel(name, pusher)

    event = 'note-update'    
    public_tweets = tweepy.api.public_timeline()
    for tweet in public_tweets:
      if tweet.id not in tweeted:
        data = {'text' : tweet.text,
                'screen_name' : tweet.user.screen_name,
                'profile_image_url' : tweet.user.profile_image_url
                }
        channel.trigger(event, data)

        t = Tweet(id = tweet.id)
        t.put()       
       

application = webapp.WSGIApplication([('/twitter', Main), ('/trigger', Trigger)], debug = True)

def main():
  run_wsgi_app(application)

if __name__ == "__main__":
  main()

デモ:http://ninoseki-lab.appspot.com/twitter
(更新は1分毎)

追記:今(11/15 22:40)ちょっと調子悪いです