はてブのRSSを表示する mixiアプリを作ってみた part.2

前回( はてぶのRSSを表示する mixiアプリを試しに作ってみた)できなかった、
Owner設定の永続化をGoogleAppEngine(JSONP)を使ってやってみた(コードは一番下)。

canvasページ

はてなのIDを入力するテキストBOXがあり、
設定済みのIDフィードをその下に表示します(未設定or空文字設定のときはHotentryを表示)。

IDを設定した時点で、mixiのownerIDと入力されたはてなIDとを紐付けてサーバ(GoogleAppEngine)に保存。
ページを開いた際は、onloadでmixiのownerIDを引数にサーバからはてなIDを取得し、そのフィードを表示します。

profileページ

ここが前回できなかった、ownerの設定をviewerに見せるところ。

ページを開いた際に、mixiのownerIDを引数にサーバからはてなIDを取得し、そのフィードを表示。

まぁ、実は自分でしか試してないから、本当にできてるかどうかは確認してないのですが・・・

homeページ

ここは前回と同じで、Hotentryを表示。


あいかわらず、ソーシャルグラフを使ってないので、次回なんか作るときは使いたいです。
あと、アクティビティAPIのあたりも使ってみたいかな。

ということで、以下はソースコード

gadgets.xml

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
  <ModulePrefs title="mixiapp x hatena_bookmark ver.2">
    <Require feature="opensocial-0.8"/>
  </ModulePrefs>
  <Content type="html" view="canvas"><![CDATA[

<form onsubmit="saveHatenaId(this); return false;" style="margin-left:20px">
input id: <input type="text" name="hatena_id" />
<input type="submit" value="click" />
</form>
<div id="title" style="font-size:18px;font-weight:bold"></div>
<div id="feed"></div>


<script type="text/javascript">
var owner_id = null;

gadgets.util.registerOnLoadHandler(init);

function init() {
    var req = opensocial.newDataRequest();
    req.add(req.newFetchPersonRequest(opensocial.IdSpec.PersonId.OWNER), 'owner');
    req.send(function(data) {
        if (!data.hadError()) {
            var item = data.get('owner');
            if (!item.hadError()) {
                var owner = item.getData();
                owner_id = owner.getId();
                
                // getHatenaId
                send('http://gae-db.appspot.com/mixi-apps/get_hatena_id?oid=' + owner_id +
                     '&cb=cb&t=' + (new Date()).getTime());
            }
        }
    });
}

function saveHatenaId(form) {
    var hid = trim(form.hatena_id.value);
    send('http://gae-db.appspot.com/mixi-apps/save_hatena_id?oid=' + owner_id +
         '&hid=' + encodeURIComponent(hid) + '&cb=cb&t=' + (new Date()).getTime());
}

function send(url) {
    var s = document.createElement('script');
    s.type = 'text/javascript';
    s.charset = 'utf-8';
    s.src = url;
    (document.getElementsByTagName('head')[0] || document.body).appendChild(s);
}

function cb(json) {
    var hid = json.hatena_id;
    var title = document.getElementById('title');
    if (json.status == 'ok' && hid != '') {
        getRSS('http://b.hatena.ne.jp/' + hid + '/rss');
        title.innerHTML = hid + ' のブックマーク';
    } else {
        getRSS('http://b.hatena.ne.jp/hotentry.rss');
        title.innerHTML = 'Hotentry';
    }
}

function trim(str) {
    return str.replace(/^[\s ]+/, '').replace(/[\s ]+$/, '');
}

function htmlEscape(str) {
    return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}

function getRSS(url) {
    var params = {};
    params[gadgets.io.RequestParameters.METHOD]       = gadgets.io.MethodType.GET;
    params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.FEED;
    params[gadgets.io.RequestParameters.NUM_ENTRIES]  = 20;  
    
    gadgets.io.makeRequest(url, function(res) {
        var html;
        if (res.data) {
            var entries = res.data.Entry;
            html = '<ul style="font-size:14px">';
            for (var i=0,len=entries.length; i<len; i++) {
                var entry = entries[i];
                html += '<li style="margin-bottom:5px"><a href="' + entry.Link + '" title="' + htmlEscape(entry.Title) + '" target="_blank">' + entry.Title + '</a>';
                html += '<a style="margin-left:5px" href="http://b.hatena.ne.jp/entry/' + entry.Link + '" target="_blank"><img border="0" style="vertical-align:middle" src="http://b.hatena.ne.jp/entry/image/' + entry.Link + '" /></a></li>';
            }
            html += '</ul>';
        } else {
            html = '<span style="margin-left:30px">ユーザが存在しない or 非公開</span>';
        }

        document.getElementById('feed').innerHTML = html;
    }, params);
}
</script>

  ]]></Content>
  <Content type="html" view="profile"><![CDATA[
<div id="title" style="font-size:18px;font-weight:bold"></div>
<div id="feed"></div>

<script type="text/javascript">
var owner_id = null;

gadgets.util.registerOnLoadHandler(init);

function init() {
    var req = opensocial.newDataRequest();
    req.add(req.newFetchPersonRequest(opensocial.IdSpec.PersonId.OWNER), 'owner');
    req.send(function(data) {
        if (!data.hadError()) {
            var item = data.get('owner');
            if (!item.hadError()) {
                var owner = item.getData();
                owner_id = owner.getId();
                
                // getHatenaId
                send('http://gae-db.appspot.com/mixi-apps/get_hatena_id?oid=' + owner_id +
                     '&cb=cb&t=' + (new Date()).getTime());
            }
        }
    });
}

function send(url) {
    var s = document.createElement('script');
    s.type = 'text/javascript';
    s.charset = 'utf-8';
    s.src = url;
    (document.getElementsByTagName('head')[0] || document.body).appendChild(s);
}

function cb(json) {
    var hid = json.hatena_id;
    var title = document.getElementById('title');
    if (json.status == 'ok' && hid != '') {
        getRSS('http://b.hatena.ne.jp/' + hid + '/rss');
        title.innerHTML = hid + ' のブックマーク';
    } else {
        getRSS('http://b.hatena.ne.jp/hotentry.rss');
        title.innerHTML = 'Hotentry';
    }
}

function htmlEscape(str) {
    return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}

function getRSS(url) {
    var params = {};
    params[gadgets.io.RequestParameters.METHOD]       = gadgets.io.MethodType.GET;
    params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.FEED;
    params[gadgets.io.RequestParameters.NUM_ENTRIES]  = 5;  
    
    gadgets.io.makeRequest(url, function(res) {
        var html;
        if (res.data) {
            var entries = res.data.Entry;
            html = '<ul style="font-size:14px">';
            for (var i=0,len=entries.length; i<len; i++) {
                var entry = entries[i];
                var t = entry.Title;
                if (t.length > 10) t = t.substring(0, 8) + '..';
                html += '<li style="margin-bottom:5px; margin-left:-20px;"><a href="' + entry.Link + '" title="' + htmlEscape(entry.Title) + '" target="_blank">' + t + '</a>';
                html += '<a style="margin-left:5px" href="http://b.hatena.ne.jp/entry/' + entry.Link + '" target="_blank"><img border="0" style="vertical-align:middle" src="http://b.hatena.ne.jp/entry/image/' + entry.Link + '" /></a></li>';
            }
            html += '</ul>';
        } else {
            html = '<span style="margin-left:30px">ユーザが存在しない or 非公開</span>';
        }

        document.getElementById('feed').innerHTML = html;
    }, params);
}
</script>

  ]]></Content>
  <Content type="html" view="home"><![CDATA[
<h3 style="margin:0; padding:0;">Hotentry</h3>
<div id="hotentries"></div>
<script type="text/javascript">
    // get rss
    var url = 'http://b.hatena.ne.jp/hotentry.rss';
    var params = {};
    params[gadgets.io.RequestParameters.METHOD]       = gadgets.io.MethodType.GET;
    params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.FEED;
    params[gadgets.io.RequestParameters.NUM_ENTRIES]  = 5;  
    
    gadgets.io.makeRequest(url, function(res) {
        //var feed    = res.data;
        var entries = res.data.Entry;
        var html = '<ul style="font-size:14px">';
        for (var i=0,len=entries.length; i<len; i++) {
            var entry = entries[i];
            var t = entry.Title;
            if (t.length > 10) t = t.substring(0, 8) + '..';
            html += '<li style="margin-bottom:5px; margin-left:-20px;"><a href="' + entry.Link + '" title="' + htmlEscape(entry.Title) + '" target="_blank">' + t + '</a>';
            html += '<a style="margin-left:5px" href="http://b.hatena.ne.jp/entry/' + entry.Link + '" target="_blank"><img border="0" style="vertical-align:middle" src="http://b.hatena.ne.jp/entry/image/' + entry.Link + '" /></a></li>';
        }
        html += '</ul>';
        
        document.getElementById('hotentries').innerHTML = html;
    }, params);

    function htmlEscape(str) {
        return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
    }
</script>
  ]]></Content>
</Module>

ページ間で共通のfunctionを外部ファイルにしてないので、重複する部分が多くて無駄に長いです・・orz。
外部ファイルにするのではなくて、共通のfunctionを定義する書き方ってあるんだろうか。

GoogleAppEngine(サーバ側)

#!-*- coding:utf-8 -*-

import wsgiref.handlers
import urllib
from google.appengine.ext import webapp
from google.appengine.ext import db

class OwnerHatebuDB(db.Model):
  owner_id   = db.StringProperty()
  hatena_id  = db.StringProperty()
  created_at = db.DateTimeProperty(required=True, auto_now_add = True)


class SaveHatenaID(webapp.RequestHandler):
  def get(self):
    oid = self.request.get('oid') or ''
    hid = self.request.get('hid') or ''
    cb  = self.request.get('cb')  or ''
    
    if oid and cb:
      id_pair = OwnerHatebuDB.all().filter('owner_id =', oid).fetch(1)
      if len(id_pair) == 0:
        new_pair = OwnerHatebuDB(owner_id = oid, hatena_id = hid)
        new_pair.put()
      else:
        id_pair[0].hatena_id = hid
        id_pair[0].put()
      
      # create json
      jsonp  = cb + '({"status": "ok", '
      jsonp += '"owner_id": "'  + urllib.quote(oid.encode('utf-8')) + '", '
      jsonp += '"hatena_id": "' + urllib.quote(hid.encode('utf-8')) + '"})'
    else:
      if not cb:
        return
      jsonp = cb + '({"status": "error"})'
    
    self.response.headers['Content-Type'] = 'text/plain; charset=utf-8'
    self.response.out.write(jsonp)

class GetHatenaID(webapp.RequestHandler):
  def get(self):
    oid = self.request.get('oid') or ''
    cb  = self.request.get('cb')  or ''
    
    id_pair = OwnerHatebuDB.all().filter('owner_id =', oid).fetch(1)
    if (len(id_pair) != 0):
      jsonp  = cb + '({"status": "ok", '
      jsonp += '"owner_id": "'  + urllib.quote(oid.encode('utf-8')) + '", '
      jsonp += '"hatena_id": "' + urllib.quote(id_pair[0].hatena_id.encode('utf-8')) + '"})'
    else:
      if not cb:
        return
      jsonp = cb + '({"status": "error"})'
    
    self.response.headers['Content-Type'] = 'text/plain; charset=utf-8'
    self.response.out.write(jsonp)


def main():
  application = webapp.WSGIApplication([
    ('/mixi-apps/save_hatena_id', SaveHatenaID),
    ('/mixi-apps/get_hatena_id',  GetHatenaID)
  ], debug=True)

  wsgiref.handlers.CGIHandler().run(application)

if __name__ == "__main__":
  main()