2010/12/31

2010年の振り返り

奇跡的にこのブログが1年以上継続できたので、2010年の振り返りをしてみることにします。

まず個人活動としては、

1月
Twitter bot「@kokucheese_bot」作成。AppEngineをPythonに乗り換える。それなりの多くの人の役に立っているようで嬉しい限り。

3月
JavaScript強化宣言。今後の主要言語として外せません。継続してフォローしていきたいと思っています。

5月
Androidアプリ「ぐぐっと!急上昇ワード」公開。その後も継続してアップデートし、現在約27,000ダウンロード。こちらも多くの人の役に立っているようで嬉しい限り。

7月
スマートフォン向けJavaScriptライブラリ「iUI」を試す。

9月
注目度がとても高いサーバーサイドJSの「Node.js」をさわってみた。

10月
スマートフォン向けJavaScriptライブラリ「JQuery Mobile」を試す。

11月
AIR for Androidで作成したアプリ「Simple Timer」を公開。
アプリ紹介ページ作成。これもAppEngine上に公開。静的ページだけでもAppEngine。

12月
Twitterのタイムラインをタイムシフト視聴するためのWebアプリケーション作成。これもAppEngineを活用。

キーワードを抽出すると、
  • Google App Engine
  • JavaScript
  • Twitter
  • スマートフォン
  • AIR/AS3
ってところでしょうか。

上記以外にも、RailsやSinatraでrubyに触れたり、NoSQLであるMongoDBを触ってみたり、スマートフォン向けサイトでhtml5&CSS3を使い始めてみたりしてます。あと、Titanium MobileでiPhone向けアプリにも挑戦中です。

仕事では、当時在職していた会社で、メイン事業の携帯アプリのデザインと新規開発の携帯アプリのデザインを手掛けたりしました。
また、たくさんの勉強会やセミナーに参加できて、仕事では関われないようなクラウド系、大規模系、インフラ系等の話が聞けたりしたのもかなりの収穫でした。

それと書籍のレビューをお手伝いし、謝辞を戴いたことも初めての経験でした。

今年1年、たくさんの方々のおかげで、小さいながらも公開できるモノを作ってこれました。この場をかりて厚く御礼申し上げます。
私も誰かの役に立てるようにできるだけ情報発信に努めたいと思っております。
そして近いうちに新しいアプリの公開やWebサービスの実現へ繋げていきたいと考えています。

今年も成長曲線を描けたと思います。(一応このブログのタイトルなので…)
そして来年も(成長曲線を描けるように)頑張りまっせ〜

2010/12/11

TwitterとGoogle App Engineの自分用Webアプリケーション

最近タイムラインがリアルタイムで追えなくなってきたり、過去にみたアレのURLが知りたいとかいったときのために、自分のHomeタイムライン(自分とフォローしている人のツイート全部)を保存して、後で閲覧できるようにしたいなぁと思っていた。
とりあえず、タイムラインのデータを取得して、保存するところまでをGoogleAppEngineで作ってcronを回していたら、2ヶ月ぐらいでDataStoreの無料枠1GB一杯になって、止まってしまっていた。

一定期間が過ぎたツイートは順次削除していくことも考えたが、せっかく貯めたデータを消してしまうのはもったいないし、こういった機能が無いから作るわけで、何か良い解決策は無いものかと思っていた。(課金してまで作ろうという発想はなかった)それと、保存したデータをどうやって閲覧するかも問題で、本気でつくると結構な作業になる。

そんなこんなで、しばらく経ったある日、よく他人のツイートを紹介するのに使われる、1ツイートだけの画面を見た時に、ここにもリツイートやら返信やらの機能があって、ツイート1つ1つに対して付与されているステータスIDとユーザ名でURLを特定できることに思い至った時に、コレでかつる!とひらめいた。

つまり、取得したタイムラインのデータを整形して、自分宛にメール送信する。ツイート文と共に上記のURLを記載しておけば、メーラーでタイムラインを追いつつ、必要があればURLからWebに飛んで、リツイート/返信/お気に入り等の機能も使える。なおかつメール形式にすることで、アーカイブと検索機能も手に入る。といったお手軽かつ要件も充分に満たしたWebアプリケーションを思いついた。(ツイートはDataStoreに登録しておき、一定時間毎にをまとめてメール。送信済みフラグの立ったのデータは順次DataStoreから削除)

現在1週間ほど稼働してますが、問題なく動いている。

そしていくつか課題も見えてきた。
1.メール送信にTaskQueueを使う
2.時刻変換の調査
3.メールをもっと見やすく
4.メンテナンス時の対応

1.メール送信にTaskQueueを使う
ここはTaskQueueを使うべきと思ってはいますが、TaskQueueをまだちゃんと理解してないので未実装。追って対応予定

2.時刻変換の調査
文字列から時刻に変換して、日本の時間に変換する。→なぜかエラーになるので調査中
(現状ツイートされた時間を日本時間に変換できていない)
※12/12 dateutilを使うことで対応。これ便利ですね。

3.メールをもっと見やすく
どんなに頑張ってもテキストだと限界がある。HTMLメールにして、読みやすくしたい
※12/12 HTMLメール対応。しかしツイート本文のリンクが…

4.メンテナンス時の対応
自分宛にメッセージ出してもしょうがないので、何の対応もしていないが、どうしますかねぇ

ぼちぼちやっていきますか。

2010/11/23

アプリの紹介ページを公開

作ろう、作ろうと思いながらも先送りしていたアプリの紹介ページをやっと作りました。
今後は大した手間がかからずに、アプリ紹介ページが作れます。

URLをみるとわかりますが、AppEngineを使っています。が、staticなサイトです。
システム化が必要なほど、たくさんのアプリは作れないと思いまして。。。(なので手動更新です)
しかし、WebアプリじゃなくてもAppEngineを使うのはアリだと思います。

ぐぐっと!急上昇ワード
Simple Timer

AppEngineでstaticなサイトを構築する場合は、app.yamlでそのように設定する必要があります。
私は、下記のように設定しています。
application: アプリ名
version: 1
runtime: python
api_version: 1

handlers:
- url: (.*)/
  static_files: htdocs\1/index.html
  upload: htdocs(.*)/index.html

- url: /(.*\.py)
  script: \1

- url: /favicon.ico
  static_files: htdocs/favicon.ico
  upload: htdocs/favicon.ico
  mime_type: image/x-icon

- url: /(.*)
  static_files: htdocs/\1
  upload: htdocs/(.*)
今回は日本語のページのみ作成していますが、アプリは世界各地に向けて配信できますので、最低でも日本語と英語のサイトは用意したいとか考えると、多言語対応のためだけでもフレームワークを使って開発した方がいいのかなと思います。

そうすると、RailsやCakeで開発した方が作りやすそうとか、AppEngineじゃない方向に行きそうな予感も。。。

2010/11/19

AIR for Androidで作成したアプリを公開してみた

Simple Timer

前回の投稿に書いた通り、AIR for Androidで比較的簡単にAndroidアプリが作成できるということがわかったので、Marketに公開できるアプリを作ってみることにしました。既にJavaで開発したアプリをリリースしていますので、オリジナルAndroidアプリ第2弾はAIRベースとなりました。

先日標準のAndroidにはタイマーアプリが無いことに気づき、とあるアプリをダウンロードしました。このアプリは非常に素晴らしい出来だったのですが、もっとシンプルなものが欲しかったので、タイマーアプリを作ることにしました。

要件
・タイマー機能に特化
・数字は大きく表示したい
・Flashベースなので自由にUIをデザインしたい


ということを元に作ったアプリはこんな画面になりました。


※左下のタイマー設定時間をタッチすると、設定画面が起動します


・99時間59分59秒まで設定可能
・タイマーの一時停止が可能
・アラーム音のON/OFFが可能
・数字を大きくしたいので、画面はランドスケープ固定
・標準のSDKのカスタマイズとはひと味違った自由なデザイン
・Android2.2以上に対応(Adobe AIRが必須)

が特徴になります。

特に設定画面はいろいろ検討した結果、数字ボタンによる入力にしました。
左右キーまたは変更したい数字をタッチすることで選択し、数字を入力します。
これは、よく見るスピナーによる入力が選択肢60個(00〜59)では多すぎると思ったからです。
どうでしょうか、なかなか面白いものができたのではないかと思っています。

※入力に関するUIをフルスクラッチするのは大変ですね
※その反面、自由に画面を作ることができるのは楽しいです

AIR for AndroidはAIRが作れる人にとっては、スキルの有効活用としてはとても良いものだと思います。
PhoneGap等のhtmlベースでアプリを作るのと同じようにパフォーマンスや全APIに対応していない等の課題もありますが、Androidアプリをデザインで差別化するには強力な手段となります。
ターゲットが2.2以上という狭さと、AIR必須という状況がユーザーに受け入れられる環境が来るかどうか、この辺りがAIR for Androidの普及にかかっていると思います。
いずれにせよ、AIR for Androidでアプリを提供するなら、標準のSDKで作成されたアプリとは違った価値を提供しなければと思いました。

※今回のアプリ作成で、Flash CS5+AS3についての理解も深まり、作れるものの幅が広がりました。
※通信して取得したデータを使うとか、カメラやGPSを使うといったアプリも作りたいですね。

2010/11/04

もうそろそろAIR for Androidについて書いておくか(ただしFlash CS5)

実はプレリリースの時に、いくつかサンプルをつくってみたりしたんですが、2.2端末が少ないこともあり放置していました。
最近、AndroidマーケットでAIRが配布開始されたり、SBMのAndroidが全機種2.2だったり、Adobe MAXの影響か、Flexの方面からの盛り上がりを感じているので、AIR for Androidについて書いておきたいと思います。

1.環境構築
Adobe CS5を普通にインストールしてあって、アップデートで最新状態であれば、FlashのエクステンションをインストールすればOKです。
つまり、下記の3つが必要になります。
・Flash CS5
・AIR2.5
・Flashエクステンション(ベータなのでAdobe Labsから)

2.アプリをつくる
正しく環境構築ができていれば、Flash CS5に 新規>テンプレート>AIR for Androidが追加されています。
このテンプレートを使って作業します。
普通にFlashをつくります。フルAS3でもタイムラインを絡めてもいいです。
UI構築が自由にできるのがいいですね。

3.パブリッシュ(apkファイルの作成)
ファイル>AIR Android設定... で専用画面がでますので、ここで諸々設定します。(今回は詳細は割愛します)
※実機をもっていれば、Flashから直接転送デバッグも可能です。

でもって、こんなのをつくってみました。
西暦を和暦に変換するアプリです。

.flaファイルに記述しているActionScriptは以下のような感じです。
普通にMouseEventを記述すれば、Android上のタッチイベントになります。
import flash.events.MouseEvent;

btn_0.addEventListener(MouseEvent.CLICK, inputNum("0"));
btn_1.addEventListener(MouseEvent.CLICK, inputNum("1"));
btn_2.addEventListener(MouseEvent.CLICK, inputNum("2"));
btn_3.addEventListener(MouseEvent.CLICK, inputNum("3"));
btn_4.addEventListener(MouseEvent.CLICK, inputNum("4"));
btn_5.addEventListener(MouseEvent.CLICK, inputNum("5"));
btn_6.addEventListener(MouseEvent.CLICK, inputNum("6"));
btn_7.addEventListener(MouseEvent.CLICK, inputNum("7"));
btn_8.addEventListener(MouseEvent.CLICK, inputNum("8"));
btn_9.addEventListener(MouseEvent.CLICK, inputNum("9"));

btn_c.addEventListener(MouseEvent.CLICK, clearAll);
btn_enter.addEventListener(MouseEvent.CLICK, calc);

function inputNum(num:String):Function {
	return function(e:MouseEvent){
		ad.appendText(num);
	} 
}

function clearAll(e:MouseEvent):void {
	ad.text = "";
	wareki.text = "";
	opt.text = "";
}

function calc(e:MouseEvent):void {
	if(ad.text.length == 4){
		var n:int = int(ad.text);
		if(n>=1868 && n<=2100){
			var year:int;
			if(n<1912){
				year = n - 1868 + 1;
				if(year == 1){
					wareki.text = "明治元年";
				}else{
					wareki.text = "明治" + year + "年";
				}
			}else if(n<1926){
				year = n - 1912 + 1;
				if(year == 1){
					wareki.text = "大正元年";
					opt.text = "※1912年 明治45年7月29日まで、7月30日より大正元年";
				}else{
					wareki.text = "大正" + year + "年";
				}
			}else if(n<1989){
				year = n - 1926 + 1;
				if(year == 1){
					wareki.text = "昭和元年";
					opt.text = "※1926年 大正15年12月24日まで、12月25日より昭和元年";
				}else{
					wareki.text = "昭和" + year + "年";
				}
			}else{
				year = n - 1989 + 1;
				if(year == 1){
					wareki.text = "平成元年";
					opt.text = "※1989年 昭和64年1月7日まで、1月8日より平成元年";
				}else{
					wareki.text = "平成" + year + "年";
				}
			}
		}else{
			wareki.text = "範囲外です";
		}
	}else{
		wareki.text = "4桁入れてね";
	}
}

/*
明治 1868年〜
大正 1912年〜
昭和 1926年〜
平成 1989年〜

1912年 明治45年7月29日まで、7月30日より大正元年
1926年 大正15年12月24日まで、12月25日より昭和元年
1989年 昭和64年1月7日まで、1月8日より平成元年
*/
普通につくったFlash/ActionScriptがAndroidで動きます。感動です。
場合によってはJavaで開発するよりも簡単かも知れません。逆にメニューキーを押したあと(イベントは取れます)の実装はすべて自前?とか、普通のAndroidアプリと似せようとすると面倒かもしれません。ゲームとか標準のUIに縛られないアプリなら良さそうです。

今後2.2端末も増えそうですし、開発の選択肢に検討してみるのもいいかもしれません。
問題はAIRランタイムの容量が大きいことだったり。。。

2010/11/03

jQueryでAjaxメモ

jQueryでAjaxする時のためのメモ(備忘)です。
「初めてのJavaScript 第2版」に出てくるサンプルをjQueryを使ったバージョンと比べてみます。

まずは、本に出てくるAjaxのサンプルです。(scriptの部分のみ抜粋)

コード量が多いし、解説が欲しいですね。

これをjQueryを使うと、かなり記述量が減り、可読性も上がります。jQuery万歳!!な感じです。

  
※jQueryから操作しやすいようにidを付け加えたりしています。
※$.getよりも$.ajaxの方が、柔軟性があって良いみたい。

通信しているサーバサイドは共通です。
(※SyntaxHighlighterの表示が少しおかしいです)

札幌" .
        "" .
        "";
        break;
      case "FU" :
        $result = "" .
        "" .
        "";
        break;
      case "NA" :
        $result = "" .
        "" .
        "" .
        "";
        break;
      case "OK" :
        $result = "" .
        "";
        break;
      defaut :
        $result = "都市が見つかりません";
        break;
    }
    echo $result;
  }
?>

上記を発展させると、イベントをトリガとして通信し、データを書き換えるという処理がいろいろできると思います。

jQuery日本語リファレンス

2010/10/26

jQuery Mobileを試してみた

JavaScriptライブラリの標準といってもいいjQueryから、モバイル向けの「jQuery Mobile」がリリースされました。
バージョンはまだALPHA 1ですが、最も期待しているライブラリですので、早速試してみました。

お題として、以前iUIを利用した「iOS向けに「ぐぐっと!急上昇ワード」のWebアプリ版をつくってみた」と同じものをつくってみました。


画面はこんな感じになりました。(リンク
iUIに比べると、リストの高さが狭かったり、文字サイズも少し小さいかな。全体的に表示が小さいですね。

コーディングのやり方はiUIと似ています。同一htmlファイル内に、各画面の記述をして、画面遷移の際にうまいこと表示してくれます。大きく違うのは、独自属性を使って各要素や動作を設定しているところでしょうか。また、戻るボタンが自動的に表示される(記述しないで良い)のには、へぇ〜って感じでした。

jQuery Mobileのページで公開されているデモを見ると、複数htmlを使って画面遷移しているようで、どうやら、ページの一部を別htmlの内容に置き換えて表示する方法があるようです。この辺の検証も追々やっていこうかと思ってます。

動作が少し重いと感じましたが、まだまだアルファ、やはり期待できるライブラリです。
jQuery Mobileの登場で、今後スマートフォン向けサイトが増えそうですね。
Webもどんどん変わってきますね。

jQuery Mobile

追記:2010/11/12 Alpha2がリリースされましたので、サンプルもAlpha2に対応させました

2010/10/07

ぐぐっと!急上昇ワード Ver.0.6.0

キーワードにあわせてサムネイル画像を表示させたい。

といわけで、今までアプリから直にデータを取得していたのですが、サーバサイドにていったん取得したデータを加工し、取得するという構成に変更しました。
今までアップデートの中で一番大変だったでのブログに書いておこうと思います。

概要はこんな感じになります。


1.キーワード情報(Google)を取得
2.取得したキーワードで画像検索(Yahoo!)を行い、サムネイル画像のURLを取得
3.キーワード情報にサムネイル画像のURLを追加したもの(JSON形式)をデータストアに保存
4.1〜3の処理をcronで定期実行
※サーバサイドはGoogle App Engine(Python)を使っています。

また、今回のアップデートで画面レイアウトの見直しも行い、増えたボタンをスッキリ整理しました。


ボタンを押して検索先を選択するようにしました。当面検索先サイトを増やす予定はないですが、増えても大丈夫なUIです。

たまにキーワードにマッチしない画像を表示したりもしますが、そこはご愛嬌ということで。

機能的には固まってきたので、今後の課題はビジュアル面かな?
まだまだやりますよ!

2010/09/13

Node.jsをさわってみた

今話題!?のNode.jsをさわってみた。

APIの日本語訳がすでにあります。アツいです。

まずインストールですが、今回はMacPortsからインストールしました。バージョンが古かったのですが(現時点での最新はv0.2.1、Portsのはv0.2)、インストール時にNode.js本体の他に依存パッケージが6つ入っていたので、無用なトラブルを避けるためにも初回はこれでよかったかも。


では手始めにAPIの日本語訳からHello Worldを。
var http = require('http');

http.createServer(function (request, response) {
  response.writeHead(200, {'Content-Type': 'text/plain'});
  response.end('Hello World\n');
}).listen(8124);

console.log('Server running at http://127.0.0.1:8124/');


次にHTML形式を返すようにしてみます。
var http = require('http');

http.createServer(function (request, response) {
  response.writeHead(200, {'Content-Type': 'text/html'});
  response.end('Hello World\n');
}).listen(8124);

console.log('Server running at http://127.0.0.1:8124/');
Content-Typeをhtmlにすれば、タグが使えるようになります。太字になりました。


ではパラメータを受け取ってみます。
var http = require('http');
var url  = require('url');

http.createServer(function (request, response) {
  response.writeHead(200, {'Content-Type': 'text/html'});
  
  // リクエストURLを解析したオブジェクトの取得
  var req_str = url.parse(request.url, true);
  
  if (req_str['query']) {
    response.end('Hello ' + req_str['query']['name'] + '\n'); //クエリnameの値にアクセス
  } else {
    response.end('Hello World!\n');
  }
  
}).listen(8124);

console.log('Server running at http://127.0.0.1:8124/');
パラメータで渡したnameが反映されて表示されてます


サーバーサイドということでまずは以上のようなことをやってみました。
次は、もっとNode.js的に期待されている非同期処理とかにも挑戦していきたいです。


※Node.js向けにnpm(Node Package Manager)というパッケージ管理の仕組みがあって、モジュールの管理ができるそうです。また、便利なモジュールの提供がされてるとか。

2010/08/31

「ぐぐっと!急上昇ワード」掲載サイト一覧

公開しているAndroidアプリ「ぐぐっと!急上昇ワード」を紹介・掲載していただいたサイトを一覧にしてみました。
たくさんのサイトでご紹介いただけて嬉しい限りです。


追加分は随時更新していきます。

2010/07/21

JavaScriptでマッシュアップ

最近学習に力を入れているJavaScriptでマッシュアップをやってみました。


どのあたりがマッシュアップ?かというと、背景画像をflickrから取ってきてランダムにbackgroundに設定しています。
ちょうど参考になる記事がありましたので、サンプルのソースを流用させて貰いました。

マッシュアップ部分のJavaScriptは以下の通りです。
※全部のソースが見たい場合は上記画像からリンク先にてご確認ください。
/*index.htmlのhead内*/
 

 

/*flickr-search.js*/
// 画像検索を行う関数
function photo_search ( param ) {
    // APIリクエストパラメタの設定
    param.api_key  = 'APIキーを取得して設定';
    param.method   = 'flickr.photos.search';
    param.per_page = 100;
    param.license  = '1,2,3,4,5,6';
    param.sort     = 'date-posted-desc';
    param.format   = 'json';
    param.jsoncallback = 'jsonFlickrApi';

    // APIリクエストURLの生成(GETメソッド)
    var url = 'http://www.flickr.com/services/rest/?'+
               obj2query( param );

    // script 要素の発行
    var script  = document.createElement( 'script' );
    script.type = 'text/javascript';
    script.src  = url;
    document.body.appendChild( script );
};

// オブジェクトからクエリー文字列を生成する関数
function obj2query ( obj ) {
    var list = [];
    for( var key in obj ) {
        var k = encodeURIComponent(key);
        var v = encodeURIComponent(obj[key]);
        list[list.length] = k+'='+v;
    }
    var query = list.join( '&' );
    return query;
}

// Flickr検索終了後のコールバック関数
function jsonFlickrApi ( data ) {
    // データが取得できているかチェック
    if ( ! data ) return;
    if ( ! data.photos ) return;
    var list = data.photos.photo;
    if ( ! list ) return;
    if ( ! list.length ) return;
    
    // ランダムに1つ取り出す
    var n = Math.floor(Math.random() * list.length);
    var photo = list[n];
    
    var link = 'http://www.flickr.com/photos/' + photo.owner + '/' + photo.id + '/';
    $('#cc').attr('href',link).text(photo.id);

    var val = 'url("http://static.flickr.com/' + photo.server + '/' + photo.id + '_' + photo.secret + '_b.jpg")';
    //$('body').css('background-image',val);
    $.backstretch(val);
}
大雑把な処理の流れとしては、どんな写真を検索するかパラメータを渡してJSのメソッドを呼びます。
JS側ではflickrApiを使うための手続きをしたり、どんな形式でレスポンスを返してもらうかなどの決め事を元に、写真を検索し、その中からランダムに返ってきた写真のURLをbackground-image jquery-backstretchに渡してブラウザのウィンドウサイズに合わせて背景(7/26追記)にセットします。

仕事ではこんな重い処理を採用することはないと思いますが、いろいろとキレイな写真が見られるので、OKとしましょう。

2010/07/01

iOS向けにiUIで「ぐぐっと!急上昇ワード」のWebアプリ版をつくってみた

以前リリースしたAndroidアプリ「ぐぐっと!急上昇ワード」のWebアプリ版をつくってみました。
どうせならということで、iPhoneっぽいUIライブラリのiUIをつかって、iOSを対象としたiPhoneアプリぽいインターフェースのWebアプリケーションになっています。


http://jp-trends.appspot.com (iOS端末またはWebkit系のブラウザでご覧ください)
自分でプログラム書く場合にデザインが二の次になるのはいかんですなぁ>俺

Androidアプリの場合はGoogleから直接XMLを取得しているのですが、Webアプリ、そしてJavaScriptということもあり、サーバ側でいったんXMLをJSONに変換しています。

Google急上昇キーワードGAEでXMLを取得しJSONに変換(cron実行)→JavaScriptでJSON取得・表示

続・ハイパフォーマンスWebサイト」によると、JSONのパースはeval()ではなくて、JSON.parse()を推奨とのことでしたので、今回そのように実装したのですが、iPod toutch(v2.2.1)やAndroid 1.6の端末ではうまくデータが取得できていないようだったので、もしかしたら、上記2機種のWebkitには、JSONのネイティブパーサが搭載されていないのかも知れません。また、iPad(v3.2)とiPhone 3GS(v4.0)で表示や挙動が若干異なる部分もあるようなので、Safari自体に差があるのか、ライブラリの問題なのか、iOSといえどもWebアプリによくある問題とは無縁ではないことを改めて実感した次第です。
今後はiUI以外のライブラリ(Senchaとか)も試してみたいですね。

2010/06/08

Androidアプリ公開後の経過報告

Androidアプリ「ぐぐっと!急上昇キーワード」を公開してから1ヶ月が経過しました。ダウンロード数は5,000を超え、アクティブ・インストール数も4,600以上と、予想を遥かに超える方々に使って戴けているようで嬉しい限りです。


また、本屋でなにげに手に取った「Xperia徹底活用ガイド」に「ぐぐっと!急上昇キーワード」が掲載されているではありませんか。


もちろん記念に購入しました。
※そうやって、掲載されたアプリ作者に買わせる作戦か!? とは思わずに、ここは素直に喜んおきます。

アプリ作って本当に良かった。

2010/05/08

Androidアプリを公開しました

「ぐぐっと!急上昇ワード」というアプリをつくり、AndroidMarketに公開しました。
Google急上昇キーワードを取得して、気になったキーワードを調べたりできるアプリです。

イメージしているUIを実現する技術力がないので、見た目がつまらないのですが、第一弾としては欲張らずに機能実装と公開を目標にして取り組みました。よかったら試してみてください。
※マーケットへのリンクの仕方が分からないので、とりあえずAndrolibのリンクを貼っておきます。ぐぐっと!急上昇ワード

最後に4/17のAndroidコードラボでお世話になりました@tmatsuoさん、チューターを務めていただきました日本Androidの会の方々に御礼申し上げます。
また、参考にさせていただきた書籍やサイトを書かれた方々もありがとうございます。

追記:
5/11 Googleリアルタイム検索に対応するバージョンを公開しました。

2010/03/24

JavaScriptを勉強しよう!

「html5を使いこなすにはJavaScriptができないといけない by 俺」ということで、まずはこの本で勉強します。

2010/01/03

Google App Engineで動かすTwitter Botをつくってみた

先日(もう去年ですが)「BOTつくろう会#5」の勉強会に参加させていただいた。@tetsunosukeさんの初心者教室でPythonでのBot作成について教えていただいたので、そのままPythonでBot作りに挑戦してみた。

最初にどうゆうBotを作ろうかと考えた時、いつも勉強会の参加時にお世話になっているATNDの新着イベントをつぶやく@atnderが頭に浮かんだ。ATND以外に「こくちーず」というサイトでもちょっとだけIT系の勉強会の告知/募集が行われている。では、@atnderの「こくちーず」版を作ってみたらどうだろうか。というわけで、Botの大枠は決まった。

次に、Botの要件をつめていこう。ATNDはAPIが公開されていて、開催イベントについて色々な情報の取得が容易になっている。一方こくちーずの方は、新着情報のRSSがある程度。まずは「新着イベントをRSSから取得し、イベント名と告知ページのURLをTweetするBot」とし、「URLはBit.lyの短縮URLに変換する」とした。

しばらくローカル環境でごにょごにょ試した結果。以下の環境に落ち着いた。
※開発はMacでやっている。PythonはMacPortsで2.6.xも入れていたが、Mac OS Xに標準で入っているVer.2.5.1(10.5の場合)でやることにした。

あと、Feedの取得/パースには結構時間がかかるようで、全ての処理をひとまとめにすると30秒制限に引っ掛かることがあったため、「Feedの取得からDataStoreの登録」「DataStoreから情報を取得しTweet」という2つの処理に分け、それぞれにCronを設定する構成にした。

ファイル構成はこんな感じ
kokucheese_bot
├app.yaml      …自動生成されたファイルを編集
├cron.yaml     …自動生成されたファイルを編集
├index.yaml    …自動生成されたファイルを編集
├default.py    …新規作成
├get_feed.py   …新規作成
├tweet.py      …新規作成
├feedparser.py …DLしたファイル
├simplejson/   …DLしたフォルダ
└twython/      …DLしたフォルダ

app.yaml
application: アプリケーション名
version: 1
runtime: python
api_version: 1

handlers:
- url: /
  script: default.py
  
- url: /get_feed
  script: get_feed.py
  
- url: /tweet
  script: tweet.py
  
- url: /.*
  script: default.py
※cronで叩けるようにプログラムとURLを関連付ける。

cron.yaml
cron:
- description: get_feed job
  url: /get_feed
  schedule: every 30 minutes

- description: tweet job
  url: /tweet
  schedule: every 30 minutes
Cronの間隔は更新頻度と負荷を考えて、とりあえず30分間隔に。これで最大1時間以内にはTweetされることになるはず。
※頻繁にFeedを取得しにいくのも悪いかなと思ってしまうのですが、Googleリーダーとかってどのくらいの間隔で取得しにいくものですかねぇ。

default.py
print "Content-Type: text/plain"
print ""
print "kokucheese_bot"
※AppEngineのURLに / でアクセスしたり、存在しないURLでアクセスしたりすると表示される。
※このBotにはWebページ的なものが必要ないので、表示はすべてprint文で手抜きした。

get_feed.py
# -*- coding: utf-8 -*-
import sys, os, re, urllib, urllib2
from datetime import *
import feedparser
import simplejson
from google.appengine.ext import db


#データモデルの定義
class EntryDataModel(db.Model):
    increment = db.IntegerProperty()   #登録数(連番)
    title     = db.StringProperty()    #イベントタイトル
    link      = db.StringProperty()    #告知ページのURL
    date      = db.StringProperty()    #Feedに登録された日時
    short_url = db.StringProperty()    #短縮済みURL
    created   = db.DateTimeProperty()  #DataStoreに登録された日時
    tweeted   = db.IntegerProperty()   #Tweet済みフラグ


#現在の登録数を返す
def get_inc_num():
    if 0 != EntryDataModel.all().count(limit=1):
        inc_num = EntryDataModel.all().order('-increment').get()
        return inc_num.increment
    else:
        return 0


#短縮URL化(bit.ly API)
def get_short_url(url):
    info_url  = "http://api.bit.ly/%s?version=2.0.1&%s=%s&login=bit.lyログイン名&apiKey=APIキー"
    url_data  = urllib2.urlopen(info_url % ("shorten", "longUrl", urllib2.quote(url))).read() #urllib2.quote(url)…パラメータ付URLもあるのでURLエンコードする
    url_info  = simplejson.loads(url_data)
    return url_info["results"][url]["shortUrl"]


#結果を表示
def check():
    request_all_query = EntryDataModel.all()
    result_set = request_all_query.order('-created').fetch(limit=50) #最新の50件
    for rs in result_set:
        print "No. "     + str(rs.increment).encode("UTF-8")
        print "Title: "  + rs.title.encode("UTF-8")
        print "Link: "   + rs.link.encode("UTF-8")
        print "Update: " + rs.date.encode("UTF-8")
        print "Short: "  + rs.short_url.encode("UTF-8")
        print "Create: " + str(rs.created).encode("UTF-8")
        print "Tweet: "  + str(rs.tweeted).encode("UTF-8")
        print ""


#メイン処理
def main():
    print "Content-Type: text/plain"
    print ""
    
    #情報入手元Feed
    feed_urls = [
        "http://kokucheese.com/main/rss/",
        #"",
        #"",
        ]
    
    #Feedから各エントリを取得し処理
    for feed_url in feed_urls:
        feed_data = feedparser.parse(feed_url) #Feedをパース
        
        #エントリの順番を並び替える
        reverse_ent = []
        reverse_ent = feed_data.entries
        reverse_ent.reverse()
        
        #現在の登録数を取得
        num = get_inc_num()
        
        for ent in reverse_ent:
            try:
                #同じタイトル及びURLが登録されているかCheck
                registed_check_query = EntryDataModel.all().filter('title =', ent.title).filter('link =', ent.link).count(limit=100)
                
                #登録がなければ、登録
                if 0 == registed_check_query:
                    t = ent.title
                    u = get_short_url(ent.link)
                    
                    num += 1
                    
                    #データストアに登録
                    feed_source = EntryDataModel()
                    feed_source.increment = num
                    feed_source.title     = t
                    feed_source.link      = ent.link
                    feed_source.date      = ent.updated
                    feed_source.short_url = u
                    feed_source.created   = datetime.utcnow() + timedelta(hours=9)
                    feed_source.tweeted   = 0
                    feed_source.put()
                    
            except:
                print "skip: " + ent.title.encode("UTF-8")
                
    check()


if __name__ == "__main__":
    main()
※Feedの取得からDataStoreへの登録
※参考にしたサイト:bit.lyのAPIを試してURLを短縮してみた
※複数のFeedを登録可能。でも増やし過ぎると30秒で完了しなくなる。そうなってくると Task Queue 使うとかロジックの見直しが必要。
※Feedのパースは Universal feed parser を使えば楽チン。GAE/Jだと ROME が動かないっぽいので、自前パーサを作るとかしないといけないかも。でもJavaでXMLを扱うならGroovyで書く方が良いと思う。
※トランザクションの使用については今後の課題。

tweet.py
# -*- coding: utf-8 -*-
import sys, os, re, urllib, urllib2
import simplejson
import twython
from google.appengine.ext import db


#データモデルの定義
class EntryDataModel(db.Model):
    increment = db.IntegerProperty()   #登録数(連番)
    title     = db.StringProperty()    #イベントタイトル
    link      = db.StringProperty()    #告知ページのURL
    date      = db.StringProperty()    #Feedに登録された日時
    short_url = db.StringProperty()    #短縮済みURL
    created   = db.DateTimeProperty()  #DataStoreに登録された日時
    tweeted   = db.IntegerProperty()   #Tweet済みフラグ


#結果を表示
def check():
    request_all_query = EntryDataModel.all()
    result_set = request_all_query.order('-created').fetch(limit=50) #最新の50件
    for rs in result_set:
        print ""
        print "Title: " + rs.title.encode("UTF-8")
        print "Tweet: " + str(rs.tweeted).encode("UTF-8")


#メイン処理
def main():
    print "Content-Type: text/plain"
    print ""
    
    #未Tweetのエントリを検索
    tweeted_check_query = EntryDataModel.all()
    untweet_set = tweeted_check_query.filter('tweeted =', 0)
    for tw in untweet_set:
        #Tweetする
        message = tw.title + u" が登録されたよ " + tw.short_url #twythonにはunicode文字列を渡す。日本語で140文字OK
        twitter = twython.core.setup(username="Twitterユーザー名", password="Twitterパスワード") #Twython v1.0
        twitter.updateStatus(message)
        
        #Tweet済みフラグを更新
        tw.tweeted = 1
        tw.put()
    
    check()


if __name__ == "__main__":
    main()
※DataStroreから未Tweet分をTweet
※Twython Ver.0.8では日本語140文字分をTweetするにはソースの修正が必要だが、Ver.1.0では必要なし。

以上のファイルをAppEngineにアップロードすればBotとして動くはずです。

RSSの他にもGoogleアラートや以前の投稿Twitterのつぶやきをキーワード検索させる等、Feedとして得られる情報はたくさんあるので、今回のBot以外にも使い方はいろいろありそうです。

今後はatnderみたいにリマインダー機能とか追加していきたいですね。

完成したBotはこちらkokucheese_bot

※そもそも勘違いしている箇所や、よりより書き方があったりする知れませんがご容赦ください。またその場合はご指摘いただければ幸いです。

※現在はTwitter側の仕様変更によりBasic認証が使えなくなりました。上記のコードそのままでは投稿できません。

※GAE+Twitter+OAuth関連のソースは『TwitterとGoogle App Engineの自分用Webアプリケーション「TwitMail」(ソース)』にも上げてあります