はてブの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を取得し、そのフィードを表示。
まぁ、実は自分でしか試してないから、本当にできてるかどうかは確認してないのですが・・・
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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); } 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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); } 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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); } </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()