ようこそゲストさん

mitc - 日記

2008/02/24(日) Remember The Milk APIの.NET用ライブラリを試作してみた

はてブ 2008/02/24 17:56 .NETmiff

はじめに

Remember The MilkRemember The Milk APIとその使い方の概要は,以前に書いたエントリ「Remember The Milk APIの使い方調査」を参照して下さい.
今回は,それを踏まえてどのようなコードを書けば実際にRemember The Milk APIを使えるのかをテストするために試作した.NET/C#製のライブラリを公開します.

Remember The Milk API利活用の現状

2007/12/15時点でのRemember The Milk API利活用界隈の情報をまとめたエントリとしてRemember The Milk API 情報 があります.
また,公式サイトのサービス一覧にもいくつかの例が載っています.
Remember the Milk Dashboard Widget for Mac OS Xなど,ソースコードを見られるものもあるのでチェックしてみるといいかも.
その他,はてなブックマークのタグ「Remember The Milk」を含む新着エントリー辺りを見ると色々あるのがわかると思います.

これらを見ていくと,Perl,Ruby,そしてPythonなど様々な言語から使ってみたコードが公開されているのがわかります.

また,.NET用のライブラリとしては,Remember the Milk .Netがあります.
これは,Flickr APIの.NET用ライブラリFlickrNet API Libraryを参考……というよりこれをRemember The Milk API用に書き直したような感じのライブラリです.
Flickr.NET API Libraryは,LGPLで公開されていますが,Remember the Milk .Netは,ライセンスその他一切の詳細情報が不明で,公開されているのもdllのみです.
ただ,オブジェクトブラウザで見た限りではFlickr.NET API Libraryと同じく完成度はかなりのもののようで,恐らくライセンスさえクリアできれば現時点では.NET上での最も使いやすいライブラリでは無いでしょうか.
興味のある方は,制作者の方に問い合わせてみると良いかもしれません.

本ライブラリについて

本ライブラリは,元々Windows Vistaサイドバー用にRemember The Milk APIを叩いてTODOリストを操作するガジェットを作成しようとしたものの,認証関係や詳細な使い方が全然わからず,公式のC言語用SDKをC#で書き換えて仕組みを勉強しようと思って作った物です.
もっとも,C言語用SDKはLinuxでの動作しか考慮しておらず*1,やはりC言語ということでC#に移植するには無駄なプロセスが多すぎたので一から書き直すことになったんですが.
習作であるため,実用するのは結構きついかもしれません.例えば,API呼び出しの戻り値はラッピングされておらずXML Documentのままだったりします.
また,私が非同期接続をどう組み込めば使いやすいのかが良くわからなかったので同期接続しかサポートしていません.
反面,Remember The Milk APIを叩くために必要な動作をトレースする操作がメインのシンプルな構成なので,
Remember The Milk APIに興味を持ったけれど,具体的にどのように操作すれば良いのか良くわからない方には,言語を問わず参考になる可能性はあります*2

*1 : そもそもHTTP接続にcurlを使っているぐらいで,Windows上ではコンパイルすらできない

*2 : C#使いにしかわかりにくいような機能はほとんどあるいは全く使っていない筈なので,一般のオブジェクト指向言語使いの方なら大抵理解できると思います

ダウンロード/更新履歴

仮に更新があった場合は,このページに載せます.

ダウンロード

RTM.NET ver.1.0

更新履歴

2008/02/24: 公開

ライセンス

こんなライブラリをソースコードをチラ見する以外に実際に使用する方が居るのかは予測できませんが,
一応ライセンスは明確化しておきます.ただ,かなり自信が無いです.

前提条件:
・本ライブラリの著作権はmiffにあります

本ライブラリは,次のどちらかのライセンスを選択して使用することができます.

1. LGPL
2.勝手に作ったライセンス
・営利/非営利問わず本ライセンスの範囲内において自由に使用して良い
・改変可能
 でも,できれば改変内容を公開してもらえると嬉しい(強制ではない)
・再配布可能
 ただし改変した場合は改変内容を明記して再配布すること
・本ライブラリに動的にリンクしたプログラムについては,本ライセンスの影響を受けない.
.本ライブラリに静的にリンクしたプログラムについては,本ライセンスの影響を受ける*3
・無保証.本ライブラリの作者は,本ライブラリを使用して発生した全ての結果(損失など全て)に対して一切の責任を負わない

こんなものを作ったのは,私のLGPLへの理解が浅いためで,Wikipediaに掲載されていた次の項目が気になったからです.
ライブラリAにリンクしたプログラムBを配布する場合、Bのライセンスにリバースエンジニアリングを禁止する条項を含めてはならない。(LGPLv2-6、LGPLv3-4)
リンクしたプログラムの方に制限を加えたくないので.
というかLGPLって結構厳しい条件な気がする.GPLよりは緩いけど.

権利関係については,完全に素人なため,記述に問題がある場合にはご指摘願えると嬉しいです.
そもそも.NET Framework上で作成して,既存のWebAPIにアクセスするだけのプログラムにどの程度創造性があるのかも不明ですし…….

*3 : 線引きが難しいので……

使い方

(一応使い方も載せておきますが,ソースコードを直接見る,オブジェクトブラウザで見る,インテリセンスで見るなどした方が早いと思います)

本ライブラリの簡単な使い方について次に示します.
サンプル中,次の定数を使っています.
定数意味
API_KEYAPI Key
SHARED_SECRETShared Secret
TOKEN有効な認証用Token
また,本ライブラリの名前空間は「RTMdotNET」です.

認証方法

まず認証用URLを作成します.
Milk myMilk = new Milk(API_KEY, SHARED_SECRET);
// このfrobは後で使うので保持しておく
string frob = myMilk.GetFrob();
// 権限はread/write/deleteのいずれか
string url = myMilk.GenerateAuthUrl("delete", frob);
次に,このURLをユーザに提示してブラウザにアクセスしてもらい*4,API_KEYを使ったアプリケーションがユーザのデータを操作することを承認して貰います.
ユーザがURLにアクセスして承認ボタンを押した後,次の処理をしてTOKENを取得します.
Milk myMilk = new Milk(API_KEY, SHARED_SECRET);
MilkAuth auth = myMilk.GetToken(frob);
string TOKEN = auth.token;
実際は恐らくGUIアプリケーションで,次のような流れになるのではないでしょうか.
URL作成→ユーザにURLを提示→ユーザに承認後ボタンか何かを押して貰う→TOKEN取得

frobは,二つの処理の間で持ち越す必要があるので,
認証URL作成とtoken取得を同じクラスで実装してfrobを保持しておくなどしておくといいと思います.
(URLと一緒にユーザに提示しておいて,token取得時に入力して貰うなど別に何でもいいんですが)
また,ここではMilkオブジェクトを処理ごとに生成していますが,実際には再生成する必要はありません.

このTOKENは,一度取得すると割とずっと使えるみたいです.
使えなくなる条件は良くわかりません.

パラメータの手動設定によるAPI呼び出し

パラメータの設定は,MilkParameterクラスのオブジェクトを通じて行います.
AddParameterでパラメータ名とその値を設定していき,MilkクラスのCallMethodメソッドに渡すことでメソッドを実行します.
戻り値はMilkResponseクラスのオブジェクトです.
Milk myMilk = new Milk(API_KEY, SHARED_SECRET);

MilkParameter myParameter = new MilkParameter();
myParameter.AddParameter("method", "rtm.test.echo");

MilkResponse myResponse = myMilk.CallMethod(myParameter);
例に挙げているメソッド「rtm.test.echo」のリファレンスを見るとわかりますが,リファレンスのAregumentsとは渡しているパラメータが異なっています.
まず,パラメータ「method」は,メソッド名を指定するために必要ですが,リファレンスでは省略されています.
次に,リファレンスでRequired(必須)とされているパラメータ「api_key」が,上記コードでは省略されています.
これは,api_keyは全てのメソッドの実行に必要なため,Milkクラスでリクエスト発行時に自動的に付加しているためです.

また,リファレンスでも上記コードでも省略されているパラメータに「api_sig」があります.
これは,リクエスト発行時のパラメータの内容から一定のルールに則って作成する文字列ですが,
api_keyと同じくMilkクラスでリクエスト発行時に自動的に付加しています.


認証が必要なメソッドの実行は,次のようになります.
Milk myMilk = new Milk(API_KEY, SHARED_SECRET);
myMilk.token = TOKEN;

MilkParameter myParameter = new MilkParameter();
myParameter.AddParameter("method", "rtm.lists.add");
myParameter.AddParameter("timeline", "19417052");
myParameter.AddParameter("name", "新規リスト");

MilkResponse myResponse = myMilk.CallAuthedMethod(myParameter);
Milkオブジェクトのtokenフィールドに認証用の文字列であるTOKENを渡し,
メソッド「CallAuthedMethod」にパラメータを渡します.
認証が必要なメソッドの場合,公式リファレンスでは省略されていますが,パラメータ「auth_token」が必要です.
ただし,メソッド「CallAuthedMethod」は,内部でパラメータ「auth_token」を追加するので,
実際にはメソッドに渡すパラメータに入れる必要はありません*5

ラッピングメソッドを使ったAPI呼び出し

本ライブラリは,rtm.reflection.getMethodsrtm.reflection.getMethodInfoを使って生成したメソッドを実装しています.
といっても,MilkParameterを使わずに公式APIリファレンスと同じ形式で呼び出せるだけのものなんですが.

例えば,前述の手動呼び出し用のサンプルを書き換えるとこのようになります.
Milk myMilk = this.GetMilk();
MilkResponse myResponse = myMilk.rtm_test_echo();
認証が必要なメソッドでも,次のように書けます.
Milk myMilk = new Milk(API_KEY, SHARED_SECRET);
myMilk.token = TOKEN;
MilkResponse myResponse = myMilk.rtm_lists_add("19417052", "新規リスト", null);
rtm.lists.addメソッドを公式リファレンスで見るとArgumentsのfilterがOptional(省略可)になっていますが,このようなパラメータは,nullを渡すことで省略できます.

これらのメソッドは,実際のAPIメソッド名の「.」を「_」に置換した名前になっています.
つまり,「rtm.lists.add」なら「rtm_lists_add」になるわけです.
メソッド名,パラメータ一覧,認証が必要かどうかなど全てrtm.reflection.methodInfoで得た情報を元に生成しているので,
もしかすると正しく動いてくれないメソッドがあるかもしれませんが,今のところ想定内の動作はしています*6

戻り値の処理

本ライブラリは,基本的にレスポンスをXmlDocumentで返すだけです.

例えば次のメソッド呼び出しを考えます.
Milk myMilk = new Milk(API_KEY, SHARED_SECRET);
myMilk.token = TOKEN;
MilkResponse myResponse = myMilk.rtm_lists_getList();
これの実体が次のXMLドキュメントだとします*7
<?xml version="1.0" encoding="UTF-8"?>
<rsp stat="ok">
	<lists>
		<list id="4321841" name="Inbox" deleted="0" locked="1" archived="0" position="-1" smart="0" sort_order="0"/>
		<list id="4321842" name="いつかやる" deleted="0" locked="0" archived="0" position="0" smart="0" sort_order="0"/>
		<list id="4321843" name="プロジェクト" deleted="0" locked="0" archived="0" position="0" smart="0" sort_order="0"/>
	</lists>
</rsp>
この場合,例えばリスト名をリストアップするなら次のようにします.
MilkResponse myResponse = myMilk.rtm_lists_getList();

List<string> ListNames = new List<string>();

System.Xml.XmlDocument xDoc = myResponse.ToXML();
System.Xml.XmlNodeList xList = xDoc.SelectNodes("/rsp/lists/list");
foreach(System.Xml.XmlNode xNode in xList){
	string name = xNode.SelectSingleNode("./@name").InnerText;
	ListNames.Add(name);
}
Remember The Milk APIの公式リファレンスでは,例えば上記のrtm.lists.getListメソッドだと,
<lists>
  <list id="100653" name="Inbox"
       deleted="0" locked="1" archived="0" position="-1" smart="0" />
  <list id="387549" name="High Priority"
       deleted="0" locked="0" archived="0" position="0" smart="1">
    <filter>(priority:1)</filter>
  </list>
  ...
</lists>
といったようにルートノードがlistsとして表記されていますが,実際はrspノードがルートノードである点は注意が必要です.

これに違和感がある場合は,
MilkResponse myResponse = myMilk.rtm_lists_getList();

System.Xml.XmlNode xRoot = myResponse.ToResponseRootNode();
System.Xml.XmlNodeList xList = xRoot.SelectNodes("./list");
このようにToResponseRootNodeメソッドによりrspノードの次のノードを取得することもできます.
ただし,ノードの付け替えを行っているわけではないので,絶対パスでの指定はやはり「rsp」からになります.

XmlDocumentの操作については,@ITなどに詳細な記事があるのでそちらを参照して下さい.
個人的にはSelectSingleNode/SelectNodesメソッドでXPath式を書くのが簡単でオススメです.
transaction
例外として,transactionだけはMilkResponseオブジェクトの操作だけで取得できます.
例えば,リストの追加をする次の処理を考えます.
Milk myMilk = new Milk(API_KEY, SHARED_SECRET);
myMilk.token = TOKEN;
MilkResponse myResponse = myMilk.rtm_lists_add("19417052", "新規リスト", null);
このレスポンスが次のXMLドキュメントだとします.
<?xml version="1.0" encoding="UTF-8"?>
<rsp stat="ok">
	<transaction id="2115266848" undoable="0"/>
	<list id="2650052" name="新規リスト" deleted="0" locked="0" archived="0" position="0" smart="0" sort_order="0"/>
</rsp>
これに対して,MilkResponseオブジェクトを通じて次の操作が可能です.
  • レスポンスがトランザクションかどうかの判断
  • Undoできるトランザクションかどうかの判断,
  • トランザクションIDの取得
例を次に示します.
Milk myMilk = new Milk(API_KEY, SHARED_SECRET);
myMilk.token = TOKEN;
// timeline作成
string timeline = myMilk.CreateTimeline();

MilkResponse myResponse = myMilk.rtm_lists_add(timeline, "新規リスト", null);

// トランザクションノードを持つかどうかの判断
if (myResponse.IsTransaction){
	// Undoできるかどうかの判断
	if (myResponse.IsUndoable){
		// トランザクションIDの取得
		string transactionId = myResponse.GetTransactionId();
		// Undo
		myMilk.Undo(timeline, transactionId);
	}
}

例外

本ライブラリは,2つの例外クラスを持っています.
また,それとは別にチェックした方が良い例外として「System.Net.WebException」があります.
MilkResponseErrorException
Remember The Milk APIのレスポンスが「stat="ok"」でなかった場合に発生します.
例外オブジェクトの「ResponseDetail」フィールドを調べることで,エラーの詳細がわかります.
「ResponseDetail」フィールドは,「MilkError」クラスのオブジェクトです.MilkErrorクラスについては後述.
具体的には,次のように使います.
Milk myMilk = new Milk(API_KEY, SHARED_SECRET);
myMilk.token = TOKEN;
try{
MilkResponse myResponse = myMilk.rtm_lists_add("19417052", "新規リスト", null);
} catch(MilkResponseErrorException ex) {
 if (ex.ResponseDetail.icode == 300){
  Console.WriteLine("タイムラインが不正です");
 } else {
   Console.WriteLine(ex.ToString());
  }
}
MilkResponseParseException
MilkResponseParseExceptionは,Remember The Milk APIのレスポンスからMilkResponseクラスのオブジェクトを生成する時に,
その文字列を想定通りのフォーマットとして解析できなかった時に発生します.
基本的にはその他のExceptionと一緒にまとめて処理してしまっても良いと思います.
System.Net.WebException
本ライブラリは,リクエストの発行に「HttpWebRequest」クラスを使用しているので,
Remember The Milk APIとうまく通信できなかった時にこの例外が発生します.

主要クラスと主要メンバ一覧

Milk
基本的に全ての処理はMilkクラスのオブジェクトを通じて行います.
メソッド概要
Milk(string apikey, string sharedsecret)API KeyとShared Secretを設定して初期化
Milk()何も設定しないで初期化(後で設定する必要有り)
MilkResponse CallMethod(MilkParameter parameter)parameterの値にapi_key,api_sigを追加しリクエストを発行
MilkResponse CallAuthedMethod(MilkParameter parameter)parameterの値にauth_token, api_key, api_sigを追加してリクエストを発行
string GetFrob()frob文字列を取得
string GenerateAuthUrl(string perms, string frob)read/write/deleteの権限文字列とfrob文字列から認証用URLを生成
MilkAuth GetToken(string frob)frob文字列から認証用tokenを取得
MilkAuth CheckToken(string userToken)認証用tokenが有効かチェック(戻り値がnullなら認証失敗)
string CreateTimeline()新規timeline作成
bool Undo(string timeline, string transaction)timelineとtransaction idを使ってUndoを試行.正否をTrue/Falseで返す
rtm_*Remember The Milk APIの呼び出し.パラメータはインテリセンスもしくはRemember The API公式リファレンスの各メソッドの項目を参照
フィールド概要
API_KEYAPI Key.コンストラクタで初期化していない場合直接値を設定する
SHARED_SECRETShared Secret.コンストラクタで初期化していない場合直接値を設定する
proxyWebProxyオブジェクト.プロキシを設定する場合は直接値を設定する.nullの場合*8は,プロキシを設定しない
timeoutタイムアウト時間をミリ秒で指定.0の場合*8は,タイムアウト時間を設定しない
token認証用token.認証が必要なメソッドを使う前には必ず設定しなければならない
MilkResponse
Remember The Milk APIからのレスポンスはこのクラスのオブジェクトで返ります.
レスポンスの解析に失敗した場合は「MilkResponseParseException」,Remember The Milk APIからのレスポンスがstat="ok"で無かった場合は「MilkResponseErrorException」が発生します.
メソッド概要
string ToString()レスポンスをそのままの形で返す
XmlDocument ToXML()レスポンスをXMLオブジェクトに変換して返す
XmlNode ToResponseRootNode()レスポンスをXMLオブジェクトに変換し,rsp以下のノードを返す
string GetTransactionId()Transaction Idがある場合はそれを返す.無い場合はnullを返す
フィールド概要
IsTransactionレスポンスがTransactionを持つかどうか
IsUndoableレスポンスがUndo可能かどうか
MilkAuth
MilkResponseを継承します.
Remember The Milk APIからのレスポンスのうち,認証関係のものを表します.
MilkクラスのGetToken,CheckTokenメソッドの戻り値です.
同じ機能でも,rtm_auth_checkToken,rtm_auth_getTokenメソッドの戻り値は,
MilkResponseクラスのオブジェクトなので注意.
フィールド概要
string token認証用token
string permstokenの権限
string idユーザのユニークID
string usernameユーザのログイン用名前
string fullnameユーザのフルネーム
MilkError
クラス「MilkResponseErrorException」のフィールド「ResponseDetail」に格納されています.
Remember The Milk APIのエラー発生時のレスポンスを表します.
フィールド概要
codeエラーコード
icodeエラーコードをint型にParseしたもの.parse失敗時は-1が返る
msgエラーメッセージ
MilkResponseErrorException
クラス「Exception」を継承します.
Remember The Milk APIからのレスポンスがstat="ok"でなかった場合に発生します.
メソッド概要
string ToString()エラーコードとエラーメッセージを一行にまとめて文字列化
フィールド概要
MilkError ResponseDetailエラーの内容を表すオブジェクト
MilkResponseParseException
実際のRemember The Milk APIレスポンスからMilkResponseオブジェクトを生成する時,
解析に失敗した場合に発生します.

*4 : アプリケーション内にブラウザを埋め込んで表示するとか標準のブラウザにURLを渡して起動するとか何でもいいですが

*5 : ただし,あらかじめMilkオブジェクトのtokenフィールドに適切な値を設定する必要はあります

*6 : 50個あるので全部試したわけではありません

*7 : idなどは架空です

*8 : デフォルト

おわりに

JavaScriptでRemember The Milk APIを叩こうとした時に認証関係が全然理解できなかったので,勢いで.NET版を作ったのでどうせだからと公開しました.はじめにの方でも書いていますが,このライブラリは色々な面で習作の域を超えていない部分があります.とはいえ,便利な機能が足りていないだけですし,改変可能なので適当に改造したらそれなりに使えるんじゃないかなと思わないこともないです.
こういう物を公開することは初めてなので,何か本ライブラリの内容*9や,本エントリ自体に問題がある場合は,コメント欄もしくはメール*10知らせて頂けると嬉しいです.

それから,自分で別のPCでdllを使ってみたんですが,最初に例外が発生した時に「ソースコードの検索」というファイルを開くためのダイアログが出ました.
キャンセルしたら以降は出なくなったんですが,今まで見たことが無かったのでなんだコレと思いました.
抑制方法とかあるのかな…….

*9 : 実装方法とかバグとか著作権侵害とか

*10 : トップページにアドレスを掲載しています


1: mawi 2008年06月19日(木) 午後4時42分

This looks interesting. I am looking for a good RTM C# API.

Is there an english summary?


名前:  非公開コメント   

E-Mail(任意/非公開):
URL(任意):
  • TB-URL  http://mitc.s279.xrea.com/adiary.cgi/074/tb/