DropboxとJabRefの組み合わせは強い

「文献管理とはBibTeXを管理することだ」という名言を残したのは直江君。彼イチ押しの管理ツールであるJabRefはWindowsでもMac OSでもUNIXでも使え、軽快な操作性で無料。私も大好きなのだがbibファイルやPDFファイルを一台の計算機に置く仕様が常用を妨げていた。かと言ってWebベースの文献管理では記入やアップロードの微妙なモタモタ感が気になってしまう。

こんな時は、JabRefの設定を変えてDropboxの管理下にファイルを置くようにしてみよう。
Dropboxは自動同期機能を持ったオンラインストレージで、無償版でも2GBまでの容量を使うことができる。テキストやPDFを置くなら、これで十分だろう。

Dropboxのインストールはこれらの記事で解説されている。

あとはJabRef側で外部ファイルの置場となるディレクトリを設定する。 Dropboxのトップディレクトリが"C:\Documents and Settings\ユーザ名\My Documents\My Dropbox" ならその下に"JabRef"、さらにその下に"PDF"のようなディレクトリを作っておき…

JabRefでOptions→Preferences→External Programsと辿り、"Main File Directory"と"Main PDF Directory"を書き換える。 (自分は"Main File Directory"のほうは使っていない)

文献とPDFファイルの追加方法は、上記設定をする前と全く同じだ。実際に追加してみて、Dropbox管理下のディレクトリにファイルが入っていくか確認しよう。

それからもちろん、PDFだけでなくbibファイルの保存場所をDropbox管理下にすることも忘れずに。 "C:\Documents and Settings\ユーザ名\My Documents\My Dropbox\JabRef"あたりが、わかりやすくて良い。

有償のEndnoteと比べればJabRefは地味な存在だが、執筆環境がTeX中心でかつ「なるべくお金かけたくない」性向を持ちがちな計算機屋さんの間では人気が高い。Dropboxがある今なら、使い続ける人が更に増えるのではないか。

【参考記事】

複数マイミクシィ認証

通常のOpenIDのRP(Relying Party)ではこんなことはしないだろうが、mixi OpenIDでは十分にありえる話だと思って試しに作ってみた。「Aさんのマイミクシィで、かつBさんのマイミクシィ」だけが使える、という複数条件による認証だ。

コードは前回のエントリ「mixi OpenIDで「マイミクシィだけの掲示板」を作ろう(Perl編)」のものを微修正し、リダイレクトを2回行うようにした。

#!/usr/local/bin/perl

use strict;
use utf8;
use CGI;
use LWP::UserAgent;
use Net::OpenID::Consumer;
use Encode;

my $query = CGI->new;
$query->charset("utf-8");

my $mixi_id1=2;    # このIDを持つ人のマイミクシィでないとログインできない
my $mixi_id2=4012; # さらに、このIDを持つ人のマイミクシィでないとログインできない
my $claimed_id1 = "https://id.mixi.jp/$mixi_id1/friends";
my $claimed_id2 = "https://id.mixi.jp/$mixi_id2/friends";
my $csr = Net::OpenID::Consumer->new(
  ua => LWP::UserAgent->new,
  args => $query,
  consumer_secret => "hoge",
  );

if($query->param('openid_url')){ # loginボタンを押された時
  if(my $cident1 = $csr->claimed_identity($claimed_id1)){
    # 1度目のリダイレクト先を決める
    my $check_url = $cident1->check_url(
      return_to  => URI->new($query->url.'?verify=1')->as_string,
      trust_root => $query->url,
      delayed_return => "checkid_setup",
      );
    # 1度目のリダイレクトをする
    print $query->redirect(-uri => $check_url);
  }
}elsif($query->param('verify')){ # 戻ってきた時
  if(my $setup_url = $csr->user_setup_url){
    print $query->redirect(-uri => $setup_url);
  }elsif($csr->user_cancel()){
    login_page($query, 'キャンセルされました');
  }elsif($query->param('openid.claimed_id')=~/https:\/\/id.mixi.jp\/$mixi_id1\/friends\/.+/){
    # 2度目のリダイレクト
    if(my $cident2 = $csr->claimed_identity($claimed_id2)){
      # SREG1.1でニックネームを取りたい
      $cident2->set_extension_args("http://openid.net/extensions/sreg/1.1", {
        required => "nickname"});
      # 2度目のリダイレクト先を決める
      my $check_url2 = $cident2->check_url(
        return_to  => URI->new($query->url.'?verify=1')->as_string,
        trust_root => $query->url,
        delayed_return => "checkid_setup",
        );
      # 2度目のリダイレクトをする
      print $query->redirect(-uri => $check_url2);
    }
  }elsif($query->param('openid.claimed_id')!~/https:\/\/id.mixi.jp\/$mixi_id2\/friends\/.+/){
    login_page($query, 'マイミクシィでないのでログインできません');    
  }elsif(my $identity = $csr->verified_identity){
    print $query->header, '<h1>ログイン完了!!</h1>'."\n";
    # ニックネームを添えて、掲示板風の画面を出す
    my $nickname = decode("utf-8",$query->param('openid.sreg.nickname'));
    if(!($nickname)){$nickname="名無し";} # ニックネームを出さない設定にしていたら「名無し」にする
    print "<h2>".$nickname."さんのオレオレ掲示板</h2>\n<form>\n";
    print "<textarea cols=70 rows=10></textarea><br>\n";
    print "<input type=\"submit\" value=\"書き込み\">\n";
    print "</form>\n";
    print "<hr>\n";
  } else {
    login_page($query);
  }
} else { # login前のページを表示する時
  login_page($query);
}

sub login_page {
    my ($query, $message) = @_;
    $message = $message ? $message= "<p class='error'>$message</p>" : '';
    print encode("utf-8",$query->header), <<PAGE;
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>mixi OpenID login</title>
</head>
<body>

<h1>オレオレ掲示板</h1>

$message

ログインしてみよう:
<a href="index.cgi?openid_url=https://mixi.jp"><img src="b_150.gif" alt="mixiでログイン" border="0"></a>
</body>
</html>
PAGE
}

これで問題なく実行できた。ガイドラインにも、そんなことをやっちゃいかんとは書かれていない。利用同意画面が2度出てくるのは面倒じゃないか?という場合には(本当にそれでよいのかという議論はありそうだが)「この外部サイトの場合常に同意する」ボタンを押してしまえばよい。

ものすごく安直な実装のため、「Aさんのマイミクシィで、かつBさんのマイミクシィ『でない』」のような要件には対応できない。2つの認証を直列につながず、別個に非同期に行えばできるかもしれない。

mixi OpenIDで「マイミクシィだけの掲示板」を作ろう(Perl編)

今日はmixi OpenIDという認証サービスが発表され、各所で報じられている。

mixiOpenIDのOPになったからと言って何がどうなるというわけでもないでしょ」と思っていた人も、「マイミクシィ認証」「コミュニティ認証」という2つの仕様には少し驚いただろう。前者は「○○さんとマイミクシィであるか」、後者は「○○というコミュニティに入っているか」をそれぞれ証明するものだ。これらを使えば、単にmixiのユーザが使えるというだけに留まらない、人や関心でのつながりを生かしたアプリケーションを作れることになる。

以下のコードはPerlCGIとして実装した「マイミクシィしか使えない掲示板」のサンプルだ。ただし今回は話を簡単にするため「掲示板っぽく見える画面を出すだけ」にしてある。本当に書き込めたり閲覧できたり、という機能を作りたい方は自分で補っていただきたい。

また、OpenIDのRPになるためにNet::OpenID::Consumerを使っている。OpenID 2.0に対応したものをSix Apartさんが配布しているので予めインストールしておく。 http://code.sixapart.com/trac/openid/browser/branches/openid2/perl/Net-OpenID-Consumer
#!/usr/local/bin/perl

use strict;
use utf8;
use CGI;
use LWP::UserAgent;
use Net::OpenID::Consumer;
use Encode;

my $query = CGI->new;
$query->charset("utf-8");

my $mixi_id=4012; # このIDを持つ人のマイミクシィでないとログインできない
my $claimed_id = "https://id.mixi.jp/$mixi_id/friends";
my $csr = Net::OpenID::Consumer->new(
  ua => LWP::UserAgent->new,
  args => $query,
  consumer_secret => "hoge",
  );

if($query->param('openid_url')){ # loginボタンを押された時
  if(my $cident = $csr->claimed_identity($claimed_id)){
    # SREG1.1でニックネームを取りたい
    $cident->set_extension_args("http://openid.net/extensions/sreg/1.1", {
      required => "nickname"});

    # リダイレクト先を決める
    my $check_url = $cident->check_url(
      return_to  => URI->new($query->url.'?verify=1')->as_string,
      trust_root => $query->url,
      delayed_return => "checkid_setup",
      );

    # リダイレクトする
    print $query->redirect(-uri => $check_url);
  }
}elsif($query->param('verify')){ # 戻ってきた時
  if(my $setup_url = $csr->user_setup_url){
    print $query->redirect(-uri => $setup_url);
  }elsif($csr->user_cancel()){
    login_page($query, 'キャンセルされました');
  }elsif($query->param('openid.claimed_id')!~/https:\/\/id.mixi.jp\/$mixi_id\/friends\/\d+/){
    login_page($query, 'マイミクシィでないのでログインできません');    
  }elsif(my $identity = $csr->verified_identity){
    print $query->header, '<h1>ログイン完了!!</h1>'."\n";
    # ニックネームを添えて、掲示板風の画面を出す
    my $nickname = decode("utf-8",$query->param('openid.sreg.nickname'));
    if(!($nickname)){$nickname="名無し";} # ニックネームを出さない設定にしていたら「名無し」にする
    print "<h2>".$nickname."さんのオレオレ掲示板</h2>\n<form>\n";
    print "<textarea cols=70 rows=10></textarea><br>\n";
    print "<input type=\"submit\" value=\"書き込み\">\n";
    print "</form>\n";
    print "<hr>\n";
  } else {
    login_page($query);
  }
} else { # login前のページを表示する時
  login_page($query);
}

sub login_page {
    my ($query, $message) = @_;
    $message = $message ? $message= "<p class='error'>$message</p>" : '';
    print encode("utf-8",$query->header), <<PAGE;
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>mixi OpenID login</title>
</head>
<body>

<h1>オレオレ掲示板</h1>

$message

ログインしてみよう:
<a href="index.cgi?openid_url=https://mixi.jp"><img src="b_150.gif" alt="mixiでログイン" border="0"></a>
</body>
</html>
PAGE
}



↑ログイン前の画面。「mixiでログイン」の画像ボタンはhttp://developer.mixi.co.jp/openid/button にあるものを使う。



↑ログインを試みるとmixiのページに飛ぶ。注意事項をよく読んで同意する。



↑ログインに成功した様子。mixiSREGによるnicknameの引渡しをサポートしているので、このようにニックネームを取得できる。「そんなもの外に出したくない!」という人はhttp://mixi.jp/openid_edit.pl により、出す出さないを選択できる。デフォルトは出すようになっている。



↑今回のキモは「マイミクシィにしかログインさせない」機能だ。これはmixiのFAQによると「mixi.jp から返ってきた Claimed Identifier がhttps://id.mixi.jp/X/friends/[ユーザー ID] であることを RP 側でチェック」せよ、とある。実際にはマイミクシィでない場合、チェックする前に上の画像のように怒られるのだが、確認用コードは入れておいたほうがよい。

Facebookアプリケーションを作ろう(JavaScript Client Library編)

昨日は東京・原宿で行われた"Facebook Developer Garage Tokyo"というイベントに参加してきた。
APIの話が概要だけで終わったりMark Zuckerbergが(昼まで日本にいたのに!)帰国した後だったり、というのは残念だったが、開発者が自らのアプリケーションをデモする場があり、何より豪華な晩飯にもありつけたので満足だ。Facebook太っ腹、Facebook大好き!

自分はまだデモできるようなものは持っていなかったのだが「OpenSocial時代のバイラル・ラーニング基盤」という大風呂敷を広げていることもあり、近いうちに開発に乗り出すことは間違いない。今回はAPIの中でもまだほとんど解説記事が書かれていない"JavaScript Client Library"を選び、ちょっとした練習を始めてみた。「自らの友人のプロフィール画像を全て取得し、ページ上にずらずら並べる」という超シンプルなものだ。

<div id='foo'></div>
<textarea style="width: 1000px; height: 300px;" id="_traceTextBox"></textarea>
<script src="http://static.ak.facebook.com/js/api_lib/v0.3/FeatureLoader.js" type="text/javascript">
</script>

<script type="text/javascript">
FB_RequireFeatures(["Api"], function(){
    // ApiClientオブジェクトを作る
    var api = new FB.ApiClient('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
                               '/~yori/facebook_js/xd_receiver.htm', null);
    // ログイン
    api.requireLogin(function(exception) {
        // 友人一覧を取る
        api.friends_get(null,function(result,exception){
            var str="";
            for(var i=0;i<result.length;i++){
                // 画像URLを取る
                api.users_getInfo(result[i],["uid","pic"],function(result2,exception){
                    // result2[0]["pic"]が何なのかわからない人は Debug.dump(result2) しよう
                    var img = result2[0]["pic"];
                    if(!img){ // 画像なかった場合
                        img = 'http://static.ak.fbcdn.net/pics/s_default.jpg';
                    }
                    str=str+'<img src="' + img + '">';
                    document.getElementById('foo').innerHTML = str;
                    // デバッグ情報
                    Debug.writeLine(result2[0]["uid"] +" " +result2[0]["pic"]);
                });
            }
        });
    });
});
</script>


↑実行例はこんな感じ(モザイク入り)、下部のtextareaにはデバッグ情報が出る。

Zipファイルでダウンロードできる文書を見るとわかる通り、このJavaScriptAPIのドキュメントは驚くほど「充実していない」。上のサンプルコード中のapi.users.getInfoの使い方は結局、REST APIの使い方の中から似た名前のものを見つけて何となく推測、という感じで動かした。「サーバサイドのプログラムが要らない」「Facebook外のどんなサイトからでもAPIを呼べる」という大きなメリットがあるのに、これは勿体無いことだ。あれこれhackしながらユーザ(開発者)が自らドキュメントを書きつつ余力があれば日本語訳も、という流れは今後起こるかもしれない。

また、コールバック関数を使った非同期処理に慣れていないと実用的なプログラムを作ろうとする上で戸惑う、という場面も想像できる。そんな時にはamachang氏が触れていた「非同期処理を同期処理のように書く方法」のお世話になることがあると思う。

昨日のイベントは、先月のMySpaceの勉強会とは色々な意味で違っていた。日本法人があるかどうかの違いなのだろう。今回の企画は(一年くらい前から考えていたらしい)日本人ユーザの有志によるものだ。会場の雰囲気は神田さんが報じている通り。オープニングにはパフォーマー集団も登場していた。

錯綜するフィード世界

「Twitterにブログの更新情報を通知する方法」という記事を見てtwitterfeedを試してみた。少しでもBlogの読者を増やしたい、と思っている人には嬉しいタイプのサービスだ。ただしこれと同じようなことを次々にやっていくと、

こんな感じで続いていくうちに「何の出力をどこに突っ込んでいるか」が、やっている本人にも把握できなくなっていく。把握できなくても困らない、という見方もできるが「購読してくれている他者」の立場を考えると、結構イヤなものかもしれない。「同じ情報を何度も叫ばないでくれ!」となるからだ。

「アグリゲータ」「アグリゲータのアグリゲータ」「アグリゲータのアグリゲータのアグリゲータ」…これらが一本のツリーにまとまってくれるならまだいい。だが実際にはそんなに綺麗にはいかない。(ループしちゃうかも?)
そう思いながら探していたらFriendFeedFeedというものがあった。これはすごい。「デンプシー破り」「デンプシー破り破り」「デンプシー破り破り破り」を思い出してしまった。

Facebook Developer Garage Tokyoに行こうじゃないか

めったに届かないFacebookのInboxに何かお知らせが、と思ったらこんなイベントへの案内が入っていた。
http://www.facebook.com/event.php?eid=14479810508

「日本で初のFacebookディベロッパーガレージが東京で開催されます」

なるほど、先月はMySpaceの勉強会だったが、API公開の流れに火をつけたFacebookも負けてはいられない。MySpaceの時とは内容も形式も異なるフラットな学び&雑談の場になるだろう、と勝手に予測している。今から楽しみになってきた。

5月19日(月)19:00〜21:00、場所は原宿。参加申込はFacebookにて。

EvernoteのToDo管理機能

「なんでもキャプチャ」「画像中の文字も検索」などが話題になっているEvernoteの中では地味な存在だが、そこに全てを集約したい人にとっては「こんな地味さこそが心地よい!」と思えるだろう。使い方はものすごく簡単、メモを作る時に右クリックし"To-Do"→"Insert Checkbox"を選ぶだけだ。

↑タスクが完了したらチェックを入れる。

↑完了か未完か、で分類され「○○というタグがついた未完のタスク」等にも簡単にアクセスできる。

↑さらに(高機能なToDo管理ツールにもない)Evernoteならではの機能は「テキスト、画像、WebクリップなどのどんなアイテムにもToDoのチェックボックスを貼り付けられる」ことだ。上の例のように「あとで読む」という使い方もできる。

「道具に使われる状態」に陥らないシンプルさで、それでいてEvernoteユーザのかゆい所に手が届く。負担が少ないので、他のToDo管理ツールを活用中の人が用途に応じて併用することも可能だ。



Evernote自体をよく知らん、という人はこちらへ。
Evernoteで自分の脳を拡張する(プライベートベータご招待)