Evernoteクリッピングの実装手順

今回、ATND暦 ver0.9.2でEvernoteへのクリッピング機能を追加しました。実は、以前SOICHAで実装したことがあるので今回は2度目になります。
当時はCocoa用のソースがiPhone用にカスタマイズされてなく改変しながら組み込んだのですが、最新バージョンではiPhone用にも使えるようになっていて、サンプルコードも用意されているので更に簡単になったように思います。

今回はEvernote API ver 1.19を利用したEvernoteクリッピングの実装手順について書きます。



APIキーの取得

EvernoteデベロッパーサイトよりAPI Keyを申請します。
http://www.evernote.com/about/developer/api/

Evernote name」はEvernoteのアカウント名を入力します。APIキーはこのアカウント名になるので個人と切り離したい場合は別にEvernoteアカウントを取得してから行なったほうがいいかもしれません。
「Organization」は会社等の組織に属していない場合は自分の名前を入力します。

Application Typeは iPhoneアプリですので「Client Application」を選択します。
「Application Details」はAPIの使い道を記載しますが、適当に「I will develop my iphone application evernote integration. I will make an application.」くらいに書いておけばいいと思います。

後は、License Agreementに同意してSUBMITします。
暫くすると、登録したメールに Consumer Key と Consumer Secret が届くので保管しておいてください。
尚、この手順で登録したKeyはあくまでsandboxサーバ上でのみ有効で、本番サーバでは有効になっていません。


Evernote API SDKのサンプルを動かしてみる

EvernoteデベロッパーサイトよりSDKをダウンロードします。

中に様々なプラットフォームのソースが梱包されていますが、iPhoneの場合はcocoa/src 以下があれば事足りますが、折角なのでサンプルソースがありますので動かしてみます。

Evernote.mファイルのヘッダ付近にある、consumerKey、consumerSecret、username、passwordを設定します。username/passwordはクリッピングする操作者のアカウント情報です。

実行して、Titleに適当に入力して「Create a new note」を押してみてください。成功した場合、サンドボックス上のEvernoteクリッピングされたことが確認できます。

https://sandbox.evernote.com/ にアクセスして確認できます。


サンプルソース

クリッピングに必要な骨組みのソースを抜き出してみました。

addNoteViewController#sendNoteEvernote

-(IBAction)sendNoteEvernote:(id)sender{
    
    // Creating the Note Object
    EDAMNote * note = [[[EDAMNote alloc] init]autorelease];
    
    // Setting initial values sent by the user
    note.title = @"test";
   
    // Simple example ENML we are going to use as content for our note
    NSString * ENML = [[[NSString alloc] initWithString: @"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE en-note SYSTEM \"http://xml.evernote.com/pub/enml2.dtd\">\n<en-note>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."]autorelease];
    
    ENML = [NSString stringWithFormat:@"%@%@", ENML, @"</en-note>"];
    NSLog(@"%@", ENML);
    
    // Adding the content & resources to the note
    [note setContent:ENML];

    // Saving the note on the Evernote servers
    // Simple error management
    @try {
        [[Evernote sharedInstance] createNote:note];
    }
    @catch (EDAMUserException * e) {
        NSString * errorMessage = [NSString stringWithFormat:@"Error saving note: error code %i", [e errorCode]];
        UIAlertView *alertDone = [[UIAlertView alloc] initWithTitle: @"Evernote" message: errorMessage delegate: self cancelButtonTitle: @"Ok" otherButtonTitles: nil];
        
        [alertDone show];
        [alertDone release];
        return;
    }
    
    // Alerting the user that the note was created
    UIAlertView *alertDone = [[UIAlertView alloc] initWithTitle: @"Evernote" message: @"Note was saved" delegate: self cancelButtonTitle: @"Ok" otherButtonTitles: nil];
    
    [alertDone show];
    [alertDone release];
}

Evernote#createNote:

- (void) createNote: (EDAMNote *) note {
    // Checking the connection
    [self connect];

    // Calling a function in the API
    [noteStore createNote:authToken :note];
}

Evernote#connect

- (void) connect {
    
    if (authToken == nil) 
    {      
        // In the case we are not connected we don't have an authToken
        // Instantiate the Thrift objects
        NSURL * NSURLuserStoreUri = [[[NSURL alloc] initWithString: userStoreUri] autorelease];
        
        THTTPClient *userStoreHttpClient = [[[THTTPClient alloc] initWithURL:  NSURLuserStoreUri] autorelease];
        TBinaryProtocol *userStoreProtocol = [[[TBinaryProtocol alloc] initWithTransport:userStoreHttpClient] autorelease];
        EDAMUserStoreClient *userStore = [[[EDAMUserStoreClient alloc] initWithProtocol:userStoreProtocol] autorelease];
 
        // Returned result from the Evernote servers after authentication
        EDAMAuthenticationResult* authResult =[userStore authenticate:username :password : consumerKey :consumerSecret];
        
        // User object describing the account
        self.user = [authResult user];
        // We are going to save the authentication token
        self.authToken = [authResult authenticationToken];
        // and the shard id
        self.shardId = [user shardId];
        
        // Creating the user's noteStore's URL
        noteStoreUri =  [[[NSURL alloc] initWithString:[NSString stringWithFormat:@"%@%@", noteStoreUriBase, shardId] ] autorelease];
        
        
        // Initializing the NoteStore client
        THTTPClient *noteStoreHttpClient = [[[THTTPClient alloc] initWithURL:noteStoreUri userAgent: @"iosdemo" timeout:15000] autorelease];
        TBinaryProtocol *noteStoreProtocol = [[[TBinaryProtocol alloc] initWithTransport:noteStoreHttpClient] autorelease];
        noteStore = [[[EDAMNoteStoreClient alloc] initWithProtocol:noteStoreProtocol] retain];
        
    }
}


EvernoteのノートはENMLというXML形式で保持される仕組みとなっています。
クリッピングするXMLデータを事前に作成する必要があります。
ENMLの仕様はこちらに記載されています。

http://www.evernote.com/about/developer/api/evernote-api.htm#_Toc297053072

ここでは EDAMNote に titleとcontentを設定して EDAMNoteStoreClient#createNote:: でノートを送信しています。
サンプルでは、authTokenは送信時に認証していますが、実際にはユーザー名、パスワードを登録する画面が必要となりますのでそこで認証させ、authTokenを保持しておけばよいかと思います。

実際に組み込む時は、cryptoとapiEvernote.h、Evernote.mを自分のプロジェクトにコピーして、固定値となっている箇所をNSUserDefaultsとかから引っ張る仕組みに組み替えます。



アクティベート
組込み後、sandbox上でテストが完了したらKey取得時にメールにURLがありますので、そこからアクティベート申請を行います。

http://www.evernote.com/about/developer/api/activate.php?code=[consumerKey]

申請して2日ほどでアクティベート完了のメールが届くので、NoteStoreとUserStoreのURLをsandboxからwwwに変更して本番サーバにてテストします。

//NSString * const userStoreUri = @"https://sandbox.evernote.com/edam/user";
//NSString * const noteStoreUriBase = @"https://sandbox.evernote.com/edam/note/"; 
NSString * const userStoreUri = @"https://www.evernote.com/edam/user";
NSString * const noteStoreUriBase = @"https://www.evernote.com/edam/note/"; 


そこまで難しい仕様(ENML)でなければ実装は認証画面を含め2、3日くらいで可能だと思います。
ENMLが厳格な仕様なのでWebのHTMLのBody内をそのままクリッピング使用とすると大抵ENMLバリデーションエラーとなります。
Evernote site memoryjavascriptを上手く活用できれば、全てのHTMLでクリッピングできそうな気がしますが、まだ試していないので余力があればチャレンジしてみます。