Twitter @Anywhereを導入(その2:コード編)

前回に続いて @Anywhere の導入について、実際のコードについて書きます。


まず、@Anywareに関する情報は、http://dev.twitter.com/anywhereにあります。

そして、日本語訳もすでにされている方がおられます。

Twitter 新 API のドキュメント「Getting Started with @Anywhere」日本語訳


今回紹介するコードは、生活チェックサービス「ChanTo.me」で使っているコードの抜粋です。


アプリケーションの登録

http://twitter.com/apps にアクセスし、「新しいアプリケーションを追加>>」をクリックしてアプリケーションを登録します。

この登録については色々なサイトに書いてありますので、注意点だけ書いておきます。


Callback URLについて

これはOAuthを使う時に利用しますが、@Anywhereでは使用されません。

ただ空白では使えませんので、「Application Website:」と同じドメインのURLを記入する必要があります。


Default Access type:

デフォルトは「Read-only」になっています。「Twitter IDへのリンク追加(Auto-linkification of @usernames)」と「Twitter IDへのツールチップの追加(Hovercards)」だけであればそれでも良いのですが、フォローとツイートボックスを使う場合は、「Read-Write」にする必要があります。


JavaScriptの読み込み

まず必要なのは当然JavaScriptの読み込みです。

コードは次のようになります。

<script src="http://platform.twitter.com/anywhere.js?id=YourAPIKey&v=1" type="text/javascript"></script>

ここの「YourAPIKey」は、先にアプリケーション登録した時に表示される「Consumer key」です。


準備はこれだけです。後は、やりたい事を書くだけです。


Twitter IDへのツールチップの追加(Hovercards)

ツールチップの例は、こちらのサイトを見てください。→ http://www.chanto.me/kumoma/

右の「プロフィール」にある「@nakachin」にマウスオーバーすると表示されます。

    twttr.anywhere(function (twitter) {
        twitter("#navi_area div.contents").hovercards();
    }

この「#navi_area div.contents」の部分は、ツールチップを表示するTwitter IDを検索する範囲を、jQuery(http://api.jquery.com/category/selectors/)のselectorの記述方法を利用して書きます。

これで、@xxx を探して「xxx」の部分をマウスオーバーするとツールチップが表示されるようになります。

当然、検索する範囲がロードした後でないと使えませんので、http://www.chanto.me/ではonloadに書いています。


単純な使い方は、これだけです。もっと詳しい使い方をしたい場合は、マニュアルをご覧ください。


ツイートボックスの表示

ツイートボックスを表示するのは以下のコードになります。

<script type="text/javascript">
    twttr.anywhere(function (twitter) {
        twitter("#twitter-dialog-tweet").tweetBox({
            label:<ツイートのラベル>,
            defaultContent: <デフォルトのツイート内容>,
            onTweet:function (tweet,renderdTweet){
                <ツイート後の処理>
            }
        });
    });

「#twitter-dialog-tweet」の部分は、ツイートボックスを表示するhtmlの部分を指定します。

labalとdefaultContentとonTweetは、通常は必要ありません。

http://www.chanto.me/では、ツイート後にダイアログを閉じるようにしているので、その処理をonTweetに書いています。

また、defaultContentは、動的に変えたいので、djangoのtemplateを使って適宜書き変えています。

ただ、前の記事にも書いた通り、ツイートボックスの入力域を選択しないと「Tweet」ボタンが押せませんのでご注意ください。


Twitter APIと@Anywhereの使い分け
ChanTo.meでは上記のように使っているのですが、Twitterアカウントでログインしているユーザがツイートする場合には、@Anywhereではなく、Twitter APIを使っています。

これは、@Anywhereを使うのであれば、Twitterアカウントでのログインしていても、また、@Anywhereにログインしないといけないためです。これがなんとかなるとうれしいんですけどねえ。たとえば、@AnywhereでもCallbackを実行してもらうようにするとか。


ログアウトのリンク表示

@Anywhereのログアウトは、以下のようなリンクで行えます。

<a href="#" onClick="twttr.anywhere.signOut();">Logout</a>

ログアウトは普通はあまりいらないと思うのですが、複数のTwitterアカウントを使っている人もいますので、あった方がいいでしょう。

本当は、ログイン状況を確認してログアウトを表示するかどうか決めたいのですが、残念ながらその部分がうまく動かないので、常にログアウトを表示しています。


マニュアルの不明点

ところで、マニュアルを見ると、twttr.anywhereのコードが以下のようになっています。

twttr.anywhere("1", function (twitter) { ...

また、こういうコードもあります。

twttr.anywhere(anywhereApiKey, "1.0.0", onAnywhereLoad);

しかし、これらの記述ではまったく動作しませんでした。このコードの意味をご存じの方がおられたら、ぜひ教えてください。


大したコードではありませんが、ご参考までにご覧ください。

Twitter @Anywhereを導入(その1)

生活チェックサービス「ChanTo.me」では、TwitterへのTweet方法は、GETのパラメータにstatus=[tweetする文]と設定したリンクを貼っていたのですが、何かと面倒なので、Twitter APIを使うつもりでいました。

ところが、@Anywhereが出て簡単にTweetをサイトからできるとの事だったので、@Anywhereの導入に向けて、調査し一部導入し公開を開始しました。

そこで、@Anywhereでの試行錯誤した点を記録します。


@Anywhereサイトを参照
こちらから@Anywhereのサイトにアクセスします。

ドキュメントを見たい場合は、右下の「Read the documentation」をクリックします。

アプリケーションを登録する場合は、「Start using now」をクリックします。(注意点を後述)

Twitter 新 API のドキュメント「Getting Started with @Anywhere」日本語訳があったのでリンクしておきます。


@Anywhereで何ができるか
@Anywhereでできるのは次の機能です。


Twitter IDへのリンク追加

html中のTwitter IDを探し出して、リンクを作成する。
つまり、html中から @nakachin とか @chantome のような部分を探し、「@」以降の部分に、twitterのURLへのリンクを作成します。


Twitter IDへのツールチップの追加

html中のTwitter IDを探し出して、Twitter情報を含むツールチップを作成する。つまり、html中から @nakachin とか @chantome のような部分を探し、「@」以降の部分にマウスオーバーすると、ツールチップが表示されます。


フォローボタンの設置

フォローボタンを設置します。フォローボタンをクリックするだけでフォローできます。

ただし、最初の一度だけ、後述のTwitterの認証と利用許可のダイアログが表示されます。


ツイートボックスの設置

ツイートボックスを設置します。ツイートを入力し「Tweet」をクリックするだけでツイートされます。

ただし、最初の一度だけ、後述のTwitterの認証と利用許可のダイアログが表示されます。


Twitter接続ボックスの設置

上記のフォローとツイートで必要となる認証ダイアログを表示するボタンを設置します。

クリックすると、認証ダイアログが表示されます。


注意点

細かいコードについては、次の記事で書きますが、注意点を書きます。


アプリケーションの登録

アプリケーションの登録は、@Anywhereからの「Start using it now」やデベロッパーページの「Register an app」からではなく、以下のリンクからやった方がいいでしょう。

[Twitterのホーム]-[設定]-[外部アプリ]-[右の開発者の方へのこちら]

URLは以下のになります。

http://twitter.com/apps/new

なぜこちらからの方が良いかというと、「Default Access type」がこちらからしか設定できないからです。上記のリンクからだと設定できず、デフォルトのRead-onlyになります。しかしRead-onlyだとフォローとツイートができません。
フォローとツイートがしたい人はこちらでRead-Writeで登録するか、上記で登録したものをこちらでRead-Writeに設定変更する必要があります。


ログイン状態とカレントユーザの取得(追記)

twitter.isConnected でログイン状態を取得でき、twitter.User.current でユーザ情報を取得できるはずなんですが、できませんでした。


Webアプリの認証との連携

ChanTo.meでは、Twitter APIのOAuthを使って認証しています。しかし、このWebアプリでの認証を@Anywhereの認証として利用する事ができません。そのため、両方行う場合は、それぞれ認証する必要があります。

ただ、上記のアプリケーションの登録自体は共有できるようです。


ツイートボックスへのフォーカス移動

ツイートボックスを利用する時、入力域にカーソルをフォーカスしないと、「Tweet」ボタンが押せません。

ツイートボックスにはデフォルト値を入れる事ができるので、わざわざフォーカスせずに「Tweet」ボタンを押したい時があるのに、できない事になってしまいます。

JavaScriptで何とかしようとしたのですが、iframeを使っており、かつ、外部サイトのコードのため、私がやった限りでは自動的にフォーカスを移す事ができませんでした。

ぜひ、何もしなくても「Tweet」ができるようにしてほしいものです。


スマートフォンでの利用

ツイートボックスだけですが、iPhoneAndroid(HT-03A)で使えるか試してみましたが、ツイートボックスは表示されませんでした。
ぜひ、早く対応してほしいものです。というのが、@Anywhereを使いたかった一番の理由は、Androidでモバイルツイッターの文字数の処理に問題があったためなもので。


他の細かい点は、コードを含めて次の記事に書きます。


生活チェックサービス「ChanTo.me」では、すでに@Anywhereに対応しました。Googleアカウントでログインした人は、@Anywhereでツイートを、Twitterアカウントでログインした人は、Twitter APIでツイートをするようになっています。ぜひご利用ください。

生活チェックサービス「ChanTo.me」をリニューアルしました。

ベータ版でオープンしていたChanTo.meですが、デザインの変更と、TwitteIDでのログイン機能の追加とを行ってリニューアルしました。

今まで、Googleアカウントに抵抗があった方もぜひご利用ください。


生活チェックサービス「ChanTo.me」

Google App Engineでメールを受信してみる。その2:Djangoの利用

前回の、Google App Engineでメールを受信してみる。その1:独自ドメインの利用の続きです。


Google App Engineでメールを受信する場合、ドキュメントにあるように、InboundMailHandlerを使うようになっています。


しかし、アプリをDjangoとHelperを使って作成していたので、webappを使ってInboundMailHandlerでメールを受けようとすると、モデルやモジュールをDjangoのアプリと共有するのが難しい状況になってしまっています。


そこでDjangoでメールを受信できないか試してみました。
その経過が以下です。


1. メールのPOSTの送信方法を確認する。
メールは、
/_ah/mail/address
というpathにhttpでPOSTされます。
どのような内容がPOSTされるのかは、http://localhost:8080/_ah/admin/の左のメニューから「Inboud Mail」を選択して表示される、メールテストツールを使いました。
firebugで、このテストツールでのPOSTの内容を確認したところ、パラメータの変数名がつくわけではなく、ただただメールのソースがそのままPOSTされる事がわかりました。


2. POSTされたデータをメールとしてオブジェクト化する。
POSTされるメールは、変数がつかずに渡されるので、request.POST['xxxx']のような形式で取得することはできません。

reuqest.raw_post_data

でメールのデータを取得します。次にこれをメールとして取得します。

  import email
  emailfeed = email.feedparse.FeedParse()
  emailfeed.feed(request.raw_post_data)
  emailobj = emailfeed.close()

後は、emailobjからget_payload()などを使ってデータを読み込めばOKです。


最後にいくつかのTIPSを書きます。

a. Subjectのデコード
Subjectのデコードはこのようにやっています。スペースの取り扱いなどで少し問題はあるかもしれませんが、一応、複数行のSubjectもunicode化できます。

    subject_list = email.Header.decode_header(emailobj.get('Subject'))
    subject = ""
    for i, v in enumerate(subject_list):
        if v[1] is None:
            subject += v[0]
        else:
            subject += unicode(v[0], v[1])


b. 添付ファイル付きメールのテスト
multipartではないメールであれば、
http://localhost:8080/_ah/admin/inboundmail
を使えばできるのですが、添付ファイルがある場合には、このツールは対応していません。

そこでこのツールを使って、工夫してテストしてみました。

まず添付ファイル付きメールを自分にメールして、そのメールをソースを取得します。

そのソースを、
http://localhost:8080/_ah/admin/inboundmail
の本文に貼り付け、To:等を必要に応じて修正します。

本文に、区切り文字を決めて、ソースの前に追加します。

アプリ側では、request.raw_post_data から区切り文字を使って、本文に貼り付けたメールを取り出し、それをメールのソースとして処理を行います。

テスト後は、この部分をコメントアウトすれば本番環境で利用できます。


と、このような形で添付ファイル付きメール受信を開発しました。
もっといい方法があれば、ぜひ教えてください。

Google App Engineでメールを受信してみる。その1:独自ドメインの利用

弊社で運営している、ライフログに活用できる生活チェックサービス「ChanTo.me」で、写真をメールで登録できる処理を追加しています。

Google App Engineでは、メール受信の機能が追加されました。

メールを受信するのは、
「string@appid.appspotmail.com」
というメールアドレスになります。


しかし、独自ドメインでサービスを提供しているので、このメールアドレスは外向けには見せたくありません。

そこで、私は独自ドメインでメールを受けて、それを「string@appid.appspotmail.com」に転送するようにしました。
ただ、メールアドレスが特定のもので数個あるくらいであれば、それぞれのメールアドレスで転送するようにすればいいだけですが、私のサービスでは不特定多数のメールアドレスが必要になります。

メールサーバを自分で立ち上げているのであれば、スクリプトをかけば何とかなりますが、メールもGoogle Appsを使っているのでそうもいきません。


ということで、苦肉の策で次のようにしました。

1. まず、独自ドメインで受信用のメールアドレス(グループ)を作成します。
例)aa@domain.com
2. これをappspotmail.comに転送するようにします。
例)転送先: aa@bb.appspotmail.com
3. 不特定多数のメールアドレスとして、1のアドレスを利用して次のように提供します。
例) aa+111@domain.com aa+222@domain.com aa+333@domain.com
4. 3のメールは、gmailの機能では、aa@domain.comに到着するので、これがaa@bb.appspotmail.comに転送されます。
5. gae側で、メールヘッダの「To」の値を見て、どのメールアドレスに送信されたメール化をチェックして処理します。


これで一応処理はできているのですが、問題があります。
1. Toに複数のアドレスが書かれている場合や、Ccを使った場合に、本来受け取ったメールアドレスがわからない。
2. Bccなどを使って送信された時など、ToやCcにアドレスが記述されていないと、受け取ったメールアドレスがわからない。
そのため、私のアプリでは、Toに一つだけしかアドレスが書かれていいる場合のみ受け付けるようにしました。


本当は、自分でメールサーバを立ち上げて、スクリプトを書くべきだとは思うのですが、まあ仕方ないかなあということで、これで今回は対応しました。

GAEのアプリ側については、後ほど、その2を書きます。

短縮URL用に数字の短縮化をPythonで書いた

我が社が提供するサービス「ChanTo.me」に、エントリーした内容をtwittertweetする機能を付けました。

といっても、APIを使って自動で書き込むのではなく、tweetの記入枠に自動的にエントリーした内容等を書き込むだけです。

どうやっているかというと、
http://twitter.com/home?status=tweetしたい内容をURLエンコードしたもの」
へのリンクを作るだけです。


そして、tweetに、エントリーのページへのリンクを追加しようと思ったのですが、
エントリーのページのURLはこのように長いものになっています。
http://www.chanto.me/kumoma/cp-6004/20100204/
つまり
http://www.chanto.me/[ユーザID]/cp-[チェックポイントID]/[日付]/
となっています。

これだと長すぎるので、短縮URLを作る事にしました。
実は、必要なのは[チェックポイントID]と[日付]だけなのです。
したがって、この数字部分を短縮する処理と元に戻す処理を作成しました。


数字を短縮する処理
言語はPythonで、下記のnumが短縮したい数字です。

chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
length = 62

result = ""
while num >= length:
    a = num % length
    b = (num - a) / length
    num = b
    result += chars[a]

result += chars[num]
return result

短縮された文字列を数字に戻す処理
言語はPythonで、下記のshortが短縮された文字列です。

chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
length = 62

x = 1
short_len = len(short)
result = 0
for i in range(0,short_len):
    result += chars.find(short[i])*x
    x = x * length
return result

これらの処理を使って短縮しています。


本当は、ホスト名も、chanto.me だけにしたいのですが、Google App Engineでは、ドメインの前に何もないというのは、残念ながらできません。
また、pathの部分も、短縮した値だけにしたいのですが、URLマッチングの関係で、それもできません。

そのため、別サーバで上げている、http://chanto.me/ 用のapacheでRedirectMatchをして、
次のようにRedirectするようにしています。

http://chanto.me/[チェックポイントIDの短縮]/[日付の短縮] 
  ↓
http://www.chanto.me/s/[チェックポイントIDの短縮]/[日付の短縮]
    ↓
http://www.chanto.me/[ユーザID]/cp-[チェックポイントID]/[日付]/

ということで、こんな感じで短くできました。
http://chanto.me/0Ib/08uwb


しかし、日付部分は決まった数字しかこないので、日付用の短縮処理を作ればさらに短くできたんですよねえ。


ところで、bit.lyなどを使わなかったのは、すべてのエントリーに短縮URLを作りたかったからです。


あまりきれいなコードではありませんが、参考まで。何か問題やご意見があればお待ちしています。

Webアプリでタイムゾーンの処理をJavaScriptで行う。

我が社が提供するサービス「ChanTo.me」では、ユーザ毎にタイムゾーンを設定できるようにしています。でもこれにはいくつか問題がありました。

  • ログインしていない場合は、デフォルトを決めてそれを利用するしかない。
  • Pythonで各地のタイムゾーンに対応できるモジュールのPytzは、ファイル数が多く、Google App Engineではできれば使いたくない。

今は、Pytzを使って実現しているのですが、Pytzを使わなくて良いようにカスタマイズ中です。

それでは、Pytzを使わないでどうするかというと、以下の手を考えました。

実現方法は、以下です。
1. サーバからは、次のようなhtmlを出力させます。

<span class="datetime" alt="Tue Feb  2 02:14:57 2010" ></span>

 altにUTCの日時を設定しています。

2. ブラウザのJavaScriptで、altをローカルのタイムゾーンに変換して表示する。

    var tmpdate = new Date();
    var tzoffset = tmpdate.getTimezoneOffset() * 60000;
    $('.datetime').each(function(idx,obj){
        var date_str = $(obj).attr('alt');
        var orgtime = new Date(Date.parse(date_str) - tzoffset);
        $(obj).html(orgtime.toLocaleString());
    });

altの日時を、一旦ローカルな時間として読み取り、それをタイムゾーンのオフセット分ずらして、本来のローカル時間にしています。

サーバ側から出力する日時のフォーマットは、都合の良い形式を使えばいいでしょう。

これで、Pytzを使わずに済むかな。