はじめに注意:今回、Evernoteで扱うデータは文字だけです。画像を追加する場合は今後調べようかなあとぼんやり思ってる感じです。
前回ではトークンを取得するところまでやったので、今回はそのトークンを使ってEvernoteを実際にあれこれ操作してみたやり方をそれぞれ書いてみます。実際にEvernoteを操作するアプリケーションを作ったときに使ったのを改めて説明している感じです。あくまでも、ドキュメント見ながらやってみたらうまくいってます、の説明です。
使うもの・動作環境・ドキュメント
前回と同じくevernote_oauthというgemを使います。動作環境は下のようになってます。
% 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)
ドキュメントは大体このへんを参照して書いていました。
だいたいAPIのリファレンス見ればわかります。そして、コレを書いていて用語集をはじめてみつけた。。。
用語集 - Evernote Developers
NoteStore取得
まず、なにをするにもNoteStoreを取得します。NoteSotreはEvernoteのノートやノートブックなど、ノート関連のデータを操作するためのすべての機能を提供しているサービスらしいです(用語集より)要はノート操作するためのAPIはすべてこのNoteStoreがもっています。なお、ユーザの情報を操作するAPIをもつのはUserStoreらしいです。(使ったことがない)
このNoteStoreはRubyではオブジェクトとして取得できます。こいつにメソッドを呼ぶ=APIを叩くことになります。NoteStoreオブジェクトを取るまでのコードがこちら。
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を取るには次のようにコードを書きます。
default_notebook_guid = note_store.getDefaultNotebook(token).guid
まずnote_store#getDefaultNotebook
メソッドでデフォルトのノートブックのオブジェクトをとってきて、それに#guid
メソッドを呼んでGUIDを取得します。メソッド名がキャメルケースなのはおそらくEvernoteのAPIに合わせてるから、というかThrift使ってるからなんでしょうねえ。
また、すべてのノートブックオブジェクトの配列はnote_store#listNotebooks
メソッドで取得できます。ノートブックの名前はノートブックオブジェクトに#name
メソッドを呼ぶことで取得できるので、ある名前のノートブックのGUIDを取りたい場合は次のようにします。
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については以下のドキュメントを見ると詳細がわかります。
とりあえず、ノートの作成 - Evernote Developersを見ると、ノートの中身の必要条件は次の2つのようです。
- 最初の2行がXMLのバージョン宣言とENML DOCTYPE宣言になっている
- ノートの本文は<en-note>...</en-note>で囲む
ここまでの話を元に、ノート作成のやり方を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取得のところからこのコードを実行すると次のようなノートが作成できます。
なお、作成するノートの設定(上のコードだとtitle, notebookGuid, content)は次のようにEvernote::EDAM::Type::Note.new
にハッシュで渡しても可能です。
note = Evernote::EDAM::Type::Note.new(:title => "ノートのタイトル", :notebookGuid => notebook_guid, :content => note_content)
今回はやってないけど、例えばタグを指定したい場合はnote#tagNames=
に対して配列でタグ名を指定すればいいみたいです。ノートオブジェクトに指定できる値については公式リファレンスのModule: Typesを参照してください。
また、ノート作成に失敗するとEvernote::EDAM::Error::EDAMUserException
やEvernote::EDAM::Error::EDAMSystemException
、Evernote::EDAM::Error::EDAMNotFoundException
(指定されたGUIDのノートブックがない)が発生するようです。(参照: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のドキュメントによるとDeprecatedらしく、かわりにnote_store#findNotesMetadata
メソッドを使えということらしいです。こっちの使い方はまだわかってないので調べてわかり次第書きたいなあと思っています。
(2014/01/11:findNotesとfindNotesMetadataの違いについて書きました→http://qiita.com/yshr04hrk/items/6c60d833a074dd108ddc )
とりあえず今回はnote_store#findNotes
の使い方を書きます。(これを書いた時点だとまだ動いています)リファレンスをざっと見た感じだと引数が増えた以外に違いはなさそう。
たとえば、"Ruby"という文字列で検索するとヒットする最初の1つのノートブックを検索したい場合はこうなります。
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クライアントで検索するときに使うような指定の仕方が可能です。また、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)を更新するときはこんな感じのコードになります。
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)
これをさっきのコードの後に実行するとこうなる。
(元のノートの中身に追加するようなコードにすればよかった……微妙に更新されたかどうかわかりにくい……)
こちらも、更新に失敗した場合はノート作成の時と同じ例外が発生します。条件に違いはありますが、投げられる例外の種類は同じようです。詳しくはドキュメントを参考にしてください。
お試し用コード
ここまでのコードを確かめるため&スクショ取る用に書いたコードも(トークンだけは変えて)載せます。先にWebインターフェースで"Qiita notebook"という名前のノートブックを作っておく必要があります。自分でやって確かめてはいるけど誰か試してみてほしい
# -*- 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で書いているので)