RubyでEvernoteを操作してみる本編（作成・検索・更新）


はじめに注意：今回、Evernoteで扱うデータは文字だけです。画像を追加する場合は今後調べようかなあとぼんやり思ってる感じです。

[前回](http://qiita.com/items/1b07303d1c15200a3f5d)ではトークンを取得するところまでやったので、今回はそのトークンを使ってEvernoteを実際にあれこれ操作してみたやり方をそれぞれ書いてみます。[実際にEvernoteを操作するアプリケーション](https://github.com/yoshihara/evemacs/)を作ったときに使ったのを改めて説明している感じです。あくまでも、ドキュメント見ながらやってみたらうまくいってます、の説明です。

# 使うもの・動作環境・ドキュメント

前回と同じくevernote_oauthというgemを使います。動作環境は下のようになってます。

```Bash
% ruby -v
ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-darwin12.3.0]

% gem list evernote_oauth 

*** LOCAL GEMS ***

evernote_oauth (0.2.1) 
```

ドキュメントは大体このへんを参照して書いていました。

* [コアとなる概念 - Evernote Developers](http://dev.evernote.com/intl/jp/start/core/)
* [クラウド API - Evernote Developers](http://dev.evernote.com/intl/jp/documentation/cloud/)
* [All declarations](http://dev.evernote.com/documentation/reference/)(APIのリファレンス)

だいたいAPIのリファレンス見ればわかります。そして、コレを書いていて用語集をはじめてみつけた。。。
[用語集 - Evernote Developers](http://dev.evernote.com/intl/jp/support/glossary.php)


# NoteStore取得

まず、なにをするにもNoteStoreを取得します。NoteSotreはEvernoteのノートやノートブックなど、ノート関連のデータを操作するためのすべての機能を提供しているサービスらしいです（用語集より）要はノート操作するためのAPIはすべてこのNoteStoreがもっています。なお、ユーザの情報を操作するAPIをもつのはUserStoreらしいです。（使ったことがない）
このNoteStoreはRubyではオブジェクトとして取得できます。こいつにメソッドを呼ぶ＝APIを叩くことになります。NoteStoreオブジェクトを取るまでのコードがこちら。

```ruby
token = "your_token"
client = EvernoteOAuth::Client.new(:token => token, :sandbox => true)
note_store = client.note_store
```

まずEvernoteOAuth::Clientオブジェクトを作成します。```:token```には前回取得したトークンを指定します。```:sandbox```はトークンで操作するEvernoteのアカウントの種類です。sandboxのアカウントのトークンならtrueを指定します。その後、```client#note_store```メソッドを呼びます。このメソッドの戻り値がNoteStoreオブジェクトになります。（やったことないけどUserStoreも似たような感じで取れるんじゃないかな？）

ちなみにnote_storeはもちろんですがtokenもこの後ずっと使います。始めてAPI使ってみたときに、```EvernoteOAuth::Client```オブジェクト作るときにトークン渡してるんだからうまいことやってくれそうな気分だったのを思い出しました。

# ノートブック取得

早速ノートを作って……ではなく、ノートブックの指定の仕方を説明しないと面倒なので先にノートブックについて書きます。というのも、ノートを作成するときには、「そのノートがどのノートブックに入るか」を指定する必要があるので、まずノートブックの話をしたいのです。
ノートブック（や、ノートなど）はそれぞれ固有のIDであるGUIDを持っています。そいつを使ってノートブックを指定する必要があります。
まずデフォルト（Evernoteクライアントだと「既定のノートブック」と表示される）のノートブックのGUIDを取るには次のようにコードを書きます。

```ruby
default_notebook_guid = note_store.getDefaultNotebook(token).guid
```

まず```note_store#getDefaultNotebook```メソッドでデフォルトのノートブックのオブジェクトをとってきて、それに```#guid```メソッドを呼んでGUIDを取得します。メソッド名がキャメルケースなのはおそらくEvernoteのAPIに合わせてるから、というかThrift使ってるからなんでしょうねえ。

また、すべてのノートブックオブジェクトの配列は```note_store#listNotebooks```メソッドで取得できます。ノートブックの名前はノートブックオブジェクトに```#name```メソッドを呼ぶことで取得できるので、ある名前のノートブックのGUIDを取りたい場合は次のようにします。

```ruby
notebook_name = "Qiita notebook" # GUIDをとりたいノートブックの名前
notebooks = note_store.listNotebooks.select do |notebook|
  notebook.name == notebook_name
end
notebook_guid = notebooks.first.guid
puts("notebook '#{notebook_name}' GUID:#{notebook_guid}")
```

次に、ここで取り出したGUIDを使ってノートを作成してみます。

# ノート作成

ノートを作成するには、まずノートのオブジェクト（Evernote::EDAM::Type::Noteオブジェクト）を作成する必要があります。このノートのオブジェクトにタイトルやらさっきのGUIDやら中身やらを指定します。その後、```note_store#createNote```メソッドに、トークンとこのノートのオブジェクトを渡すとノートが作成されます。コードにするとこんな感じになります。

ここで、ノートの中身はENMLという、Evernote独自のXML拡張に従う必要があります。（従ってない場合は```note_store#createNote```メソッド実行時に例外が起きる）ENMLについては以下のドキュメントを見ると詳細がわかります。

* [ENML - Evernote Developers](http://dev.evernote.com/intl/jp/start/core/enml.php)

とりあえず、[ノートの作成 - Evernote Developers](http://dev.evernote.com/intl/jp/start/core/creating_notes.php)を見ると、ノートの中身の必要条件は次の2つのようです。

* 最初の2行がXMLのバージョン宣言とENML DOCTYPE宣言になっている
* ノートの本文は&lt;en-note&gt;...&lt;/en-note&gt;で囲む

ここまでの話を元に、ノート作成のやり方をRubyで書くとこうなった。

```ruby
ENML_HEADER = <<HEADER
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">
HEADER

note_content = <<CONTENT
#{ENML_HEADER}
<en-note>Hello, my Evernote (from Ruby)!</en-note>
CONTENT

note = Evernote::EDAM::Type::Note.new
note.title        = "ノートのタイトル"
note.notebookGuid = notebook_guid
note.content      = note_content

note_store.createNote(token, note)
```

NoteStore取得のところからこのコードを実行すると次のようなノートが作成できます。

![screenshot.png](https://qiita-image-store.s3.amazonaws.com/0/18011/4ecbfccc-3dd9-daf7-4725-ec0907e68a30.png)

なお、作成するノートの設定（上のコードだとtitle, notebookGuid, content）は次のように```Evernote::EDAM::Type::Note.new```にハッシュで渡しても可能です。

```ruby
note = Evernote::EDAM::Type::Note.new(:title => "ノートのタイトル", :notebookGuid => notebook_guid, :content => note_content)
```

今回はやってないけど、例えばタグを指定したい場合は```note#tagNames=```に対して配列でタグ名を指定すればいいみたいです。ノートオブジェクトに指定できる値については公式リファレンスの[Module: Types](http://dev.evernote.com/documentation/reference/Types.html#Struct_Note)を参照してください。

また、ノート作成に失敗すると```Evernote::EDAM::Error::EDAMUserException```や```Evernote::EDAM::Error::EDAMSystemException```、```Evernote::EDAM::Error::EDAMNotFoundException```（指定されたGUIDのノートブックがない）が発生するようです。（参照：[createNoteのドキュメント](http://dev.evernote.com/documentation/reference/NoteStore.html#Fn_NoteStore_createNote)）実際に使うときは、これらに対して例外処理するとよさそうです。[1]
[1]:（と言いながら自分が作っているアプリケーションのコードを直す）

なお、ノートのGUIDも```note#guid```で取得できます。ただし、（当たり前といえば当たり前ですが）一度EvernoteにノートをアップロードしないとGUIDはnilになります。（なので、ここで作ったnoteに```#guid```メソッドを呼んでもnilが返ってくる）

# ノート検索

ノート検索はEvernote::EDAM::NoteStore::NoteFilterオブジェクトを作って、```note_store#findNotes```メソッドにトークンやその他オプションと渡すと、条件に当てはまったノートのリストを表すEvernote::EDAM::NoteStore::NoteListオブジェクトが返ってきます。

ただ、```note_store#findNotes```メソッドは[findNotesのドキュメント](http://dev.evernote.com/documentation/reference/NoteStore.html#Fn_NoteStore_findNotes)によるとDeprecatedらしく、かわりに```note_store#findNotesMetadata```メソッドを使えということらしいです。こっちの使い方はまだわかってないので調べてわかり次第書きたいなあと思っています。

(2014/01/11：findNotesとfindNotesMetadataの違いについて書きました→http://qiita.com/yshr04hrk/items/6c60d833a074dd108ddc ）

とりあえず今回は```note_store#findNotes```の使い方を書きます。（これを書いた時点だとまだ動いています）リファレンスをざっと見た感じだと引数が増えた以外に違いはなさそう。

たとえば、"Ruby"という文字列で検索するとヒットする最初の1つのノートブックを検索したい場合はこうなります。

```ruby
filter = Evernote::EDAM::NoteStore::NoteFilter.new
filter.words = "Ruby"

found_note = note_store.findNotes(token, filter, 0, 1).notes.first
p found_note #=> <Evernote::EDAM::Type::Note …>
```

まず```Evernote::EDAM::NoteStore::NoteFilter.new```でフィルタオブジェクトを作ります。で、```filter#words=```メソッドに対して検索する文字列を指定します。この文字列は[Evernoteクライアントで検索するときに使うような指定の仕方](http://dev.evernote.com/intl/jp/documentation/cloud/chapters/search_grammar.php)が可能です。また、```filter.notebookGuid=```メソッドを使ってノートブックのGUIDをを指定すると、そのノートブック内で検索します。

その後、```note_store#findNotes```メソッドに次の引数を指定すると、検索結果（Evernote::EDAM::NoteStore::NoteListオブジェクト）が返ってきます。

* トークン（token）
* フィルタオブジェクト（filter）
* オフセット（0）
* 最大ノート数（1）

ここでいうオフセットはあまりよくわかってないです。リファレンスを見るとページネーションで使われるらしいので、最初のノートの位置ぐらいに思っています。ここでは最初から出してほしいので0を指定します。また、最大ノート数は検索結果に最大何個のノートを含んでほしいかを指定します。今回は1つでいいので1。（```note_store#findNoteCounts```メソッドを使うと、フィルタオブジェクトで指定した検索条件に当てはまるノートが各ノートブックや各タグにどれくらいあるかわかるらしいので、そのへんを使うと最大ノート数に指定できる最大値がわかりそうです。試してないのでわかりませんが。）

最後から2行目では、検索結果のNoteListオブジェクトに```#notes```メソッドを呼ぶとノートの配列が返ってくるので、その最初のノートを取り出しています。ただし、この配列の要素であるノートは、ノートの中身contentや、画像データ等のリソースは保持していません。これらを取得するにはまた別のメソッドを呼ぶ必要があります。（TODO: おいおいまとめたい）

# ノート更新

すでにあるノートの中身を更新したいときは、新しいノートオブジェクトを作って、```note_store#updateNote```メソッドにトークンと一緒に渡します。この時つくる新しいノートは、更新したいノートのGUIDを指定します。
さっき検索して引っかかったノート（found_note）を更新するときはこんな感じのコードになります。

```ruby
new_note = Evernote::EDAM::Type::Note.new
new_note.title   = "#{found_note.title}を変えてみました"
new_note.guid    = found_note.guid
new_note.content = <<CONTENT
#{ENML_HEADER}
<en-note>
<h1>Hello Evernote!</h1>
<div>This is an example for my entry in Qiita.</div>
</en-note>
CONTENT

note_store.updateNote(token, new_note)
```

これをさっきのコードの後に実行するとこうなる。

![screenshot.png](https://qiita-image-store.s3.amazonaws.com/0/18011/a28354c0-1931-d044-4ad6-16afda5bd8d6.png)

（元のノートの中身に追加するようなコードにすればよかった……微妙に更新されたかどうかわかりにくい……）
こちらも、更新に失敗した場合はノート作成の時と同じ例外が発生します。条件に違いはありますが、投げられる例外の種類は同じようです。詳しくは[ドキュメント](http://dev.evernote.com/documentation/reference/NoteStore.html#Fn_NoteStore_updateNote)を参考にしてください。

# お試し用コード

ここまでのコードを確かめるため＆スクショ取る用に書いたコードも（トークンだけは変えて）載せます。先にWebインターフェースで"Qiita notebook"という名前のノートブックを作っておく必要があります。<del>自分でやって確かめてはいるけど誰か試してみてほしい</del>

```ruby:evernote_sample.rb
# -*- coding: utf-8 -*-
require "evernote_oauth"

puts("NoteStore取得")
token = "your_token"
client = EvernoteOAuth::Client.new(:token => token, :sandbox => true)
note_store = client.note_store

puts("ノートブック取得")
notebook_name = "Qiita notebook" # GUIDをとりたいノートブックの名前
notebooks = note_store.listNotebooks.select do |notebook|
  notebook.name == notebook_name
end
notebook_guid = notebooks.first.guid
puts("notebook '#{notebook_name}' GUID:#{notebook_guid}")

puts("ノート作成：")

ENML_HEADER = <<HEADER
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">
HEADER

note_content = <<CONTENT
#{ENML_HEADER}
<en-note>Hello, my Evernote (from Ruby)!</en-note>
CONTENT

note = Evernote::EDAM::Type::Note.new
note.title        = "ノートのタイトル"
note.notebookGuid = notebook_guid
note.content      = note_content

note_store.createNote(token, note)
puts("ここでスクショ取る")
gets

puts("ノート検索：")
filter = Evernote::EDAM::NoteStore::NoteFilter.new
filter.words = "Ruby"

found_note = note_store.findNotes(token, filter, 0, 1).notes.first
p found_note #=>

puts("ノート更新：")

new_note = Evernote::EDAM::Type::Note.new
new_note.title   = "#{found_note.title}を変えてみました"
new_note.guid    = found_note.guid
new_note.content = <<CONTENT
#{ENML_HEADER}
<en-note>
<h1>Hello Evernote!</h1>
<div>This is an example for my entry in Qiita.</div>
</en-note>
CONTENT

note_store.updateNote(token, new_note)
puts("ここでスクショとったら完了")
```

# まとめ

なにかの参考になれば。自分でも探り探りコードは書いているので、わかったことがあり次第まとめてアウトプットしたいです。
あと、Qiitaでスクショが貼れるようになってとてもうれしいです。Kobitoで対応してくれると助かります。（大体Kobitoで書いているので）