Evernote

2012年09月09日

ぼくはEvernoteが大好きで、長年使っている。
そして、vimのカスタマイズが徐々に楽しくなってきている今日この頃である。
そこで考えるのが、vimからEvernoteを使いたい、という発想。
当然、偉大な先人がいて、Evervimというツールを作ってくれていた。
@kakkyz81さんに感謝。

サーバ作業中のログやコマンドのリストなど、テキストを一時保存する機会は多い。ひととおり作業が終わると、その結果や途中のログなどを、ブラウザのEvernoteにコピペしていたが、これで直接vim上からEvernoteに記録することができる。なんて便利!

早速、Mac OSX Mountain Lionに入れてみるものの、なかなか苦労した。 こちらのドキュメントには、

 python及び+pythonでコンパイルされたvim及び
pythonのmarkdownパッケージが必要である。 

とあるが、vimでpythonがちゃんと動いてくれなかったので、結局パッケージ管理のhomebrewを導入し、pythonを入れ、vimも入れることとなった。

ついでにOpenBrowser.vimも入れる。これはvim上からURLやメールアドレスの文字列にカーソルをおき、このプラグインを起動すると、ブラウザでそのURLを開いてくれる機能を追加する。便利だ。
これを入れておくと、Evervimからノートをブラウザで開くことができるようになる。

Evervimでできることは、とてもシンプルであり、必要最小限である。
主たる機能は、以下の6つ。(全ての機能は、上記ドキュメントを参照のこと)

:EvervimNotebookList ノートブックの一覧を表示する。
:EvervimListTags タグの一覧を表示する。
:EvervimSearchByQuery {query} 検索文字列を指定して、ヒットしたノートの一覧を表示する。
:EvervimCreateNote 新規ノートを保存するためのバッファが開かれる。
:EvervimOpenBrowser ノートをブラウザで開く。
:EvervimSetup アカウントのセットアップを行う。

これらのコマンドを、.vimrcにショートカットに定義する。 ぼくは"\"のあとのアルファベットとした。
検索文字列は、\sと打ったあと、続けて入力するようにする。

" Evervim
nnoremap <Leader>l :EvervimNotebookList<CR>
nnoremap <Leader>s :EvervimSearchByQuery<Space>
nnoremap <Leader>c :EvervimCreateNote<CR>
nnoremap <Leader>b :EvervimOpenBrowser<CR>

" open browser
nnoremap <Leader>o <Plug>(openbrowser-smart-search) 

これで環境設定は終了であるが、これはEvernoteクライアントのエディタを変更したと解釈してもよい。なんてすばらしいのでしょう。

こんどはubuntuのvimも同じ設定をしておこう。

余談であるが、vimでyankしたときに、ウィンドウシステムのクリップボードに入ってくれるといいと思ったが、今回vimを入れ直したことで、それも可能となった。 詳しくは、こちらの解説を。

vimエディタからクリップボードを利用する。

OS Xの標準vimは、+clipboardのオプションを付けてコンパイルされていなかったが、homebrewでhomebrew/dupesのリポジトリから入れたvimは、+clipboardになっていた。嬉しい。



sylphide_ffr31mr at 00:24コメント(0)トラックバック(0) 

2011年02月17日

google app engine上でEvernote連携のwebサービスを作るには、OAuthでの認証が必要になる。
このエントリーでは、OAuthをGoogle AppEngine上でpythonを使って認証するサンプルプログラムcunsumer_oauth.pyを提供する。
OAuthの直接的な部分は、google codeで提供されているoauth.pyを使い、それの呼び出し方を記述しているのが、consumer_oauth.pyである。
このプログラムは、oauth.pyのライブラリの使い方の参考とすることを目的としているので、元のoauth.pyライブラリに則り、MITライセンスとする。

oauth.pyを使ったサンプルプログラムは、webで調べるとtwitter向きのものが多い。
しかし、Evernoteをproviderとしたとき、同じOAuthでも、twitterとevernoteではパラメータの扱いが若干違うため、そのままは使えない。 そのあたりを修正して、ここにあげておく。

1. OAuthの仕組み

まずは、OAuthの基本的な動きを理解する。

2.ライブラリの入手

google codeでは、pythonのOAuthライブラリが ここ で公開されている。

oauth.pyを入手

ここでは、exampleとしてoauth.pyを使った client.py というのもあるのだが、これがどうもわかりづらい。
いろいろ探っているうちに、わかりやすいexampleをみつけた。

GAE+OAuth

このexampleは、いろいろミスがあり、動かすためにはかなりの修正が必要となるが、oauth.pyの使い方はとても参考になった。おかげで、oauth.pyを使えたといっても過言ではない。

3.EvernoteからAPI keyの取得

実際に、以下のプログラムを動かすには、Evernoteからconsumer用API keyを入手しなくてはならない。

以下のフォームからEvernoteにAPI keyを申請する。

https://www.evernote.com/about/developer/api/

application typeは"web application"とすること。
1,2日でメールで届くはずである。

4.Evernote OAuthの特徴

Twitterなど、あちこちで公開されているOAuthの使用例と比べて、Evernoteでは、以下の2点を注意する必要がある。

  1. twitter OAuthなどでは、request tokenからaccess tokenに交換するために、承諾されたoauth_okenを送るが、evernoteの場合は、同時にverifierも送らないといけない。
    verifierは、エンドユーザが承諾ボタンを押した後のcallback urlで戻ってくるhttp getリクエストの中に、パラーメータとして格納されている。

  2. 承諾画面より後はevernoteではoauth_secretを使わないので、url encodeされたパラメータ部分で&oauth_secret=&、と値部分がnullになるリクエストが一度だけくる。

1.については、verifierパラメータを抜き出して、リクエストURLを生成時する関数にパラメータを渡すことで対処できる。oauth.pyのライブラリは、verifierをリクエストに加える機能を持っている。

2.については、secretパラメータがnullになるとoauth.pyの中でエラーとなる。ouath.pyの以下の行でエラーを補足し、secretにダミーの文字列をセットすることで対処する。

5.oauth.py変更箇所

137行目から

(変更前)
    def from_string(s):
        """ Returns a token from something like:
        oauth_token_secret=xxx&oauth_token=xxx
        """
        params = cgi.parse_qs(s, keep_blank_values=False)
        key = params['oauth_token'][0]
        secret = params['oauth_token_secret'][0]
        token = OAuthToken(key, secret)
        try:
            token.callback_confirmed = params['oauth_callback_confirmed'][0]
        except KeyError:
            pass # 1.0, no callback confirmed.
        return token

(変更後)
    def from_string(s):
        """ Returns a token from something like:
        oauth_token_secret=xxx&oauth_token=xxx
        """
        params = cgi.parse_qs(s, keep_blank_values=False)
        key = params['oauth_token'][0]
        try:
            secret = params['oauth_token_secret'][0]
        except KeyError:
            secret = "dummy"
        token = OAuthToken(key, secret)
        try:
            token.callback_confirmed = params['oauth_callback_confirmed'][0]
        except KeyError:
            pass # 1.0, no callback confirmed.
        return token

6.Access Tokenの保存

consumer webサービスを使うたびに、毎回evernoteで承諾をするのが面倒なので、承諾済みであることをどこかに記録しておきたい。

クッキーを使ってブラウザに保存する方法と、consumerサーバ上に保存する方法がある。
今回作成したconsumer_oauth.pyでは、google app engineのデータストアに保存することにする。 そのため、consumerサービスを使うときは、googleアカウントでのログインを必須とし、GAE上のデータストアにgookeアカウントにひもづくEvernote access tokenを保存することにした。
本当のwebサービスでは、プライバシー保護としてaccess tokenを保存しないオプションを用意する予定である。

7.consumer_oauth.pyの実行

変更済みのoauth.pyとapp.yamlもセットにして、consumer_oauth.pyをgithubで公開したので、以下からダウンロードする。

consumer_oauth.pyのソース

GAE開発キットが動作する環境であれば、githubからダウンロードした3つのファイルを同一ディレクトリにおいて、開発サーバを起動すれば、ブラウザからアクセスできる。

注意点は、以下の2点である。

  1. evernote開発環境のアカウント
  2. Evernoteでの承諾は、evernoteログインが必要である。(当然だが) このプログラムでは、URLをEvernoteの開発サーバ(sandbox.evernote.com)に指定しているので、本番サーバ(www.evernote.com)のアカウントとは別に、開発サーバ用にユーザ登録をしないとEvernoteにログインはできない。

  3. Callback URL
  4. callback URLは、GAE開発環境(localhost:8080)をデフォルトとしている。GAE上にアップロードする前には、.appspot.com名のホスト名に修正してからアップロードする必要がある。

 

正常に動作すれば、以下のような画面がブラウザでみれるはず。

1.googleにもEvernoteで承認されていない状態

1-2

2.Googleにログインし、Evernoteで承認されていない状態

2-2

3.Evernoteで承認する画面

4-2

4.Evernoteに承認された状態

3-2

これ以降、このoauth_keyをEdamに格納し、Edam_userIdのディレクトリに向かってAPI を呼び出す部分を作ることで、consumerからノートの操作が可能となる。

Endpoint URL: https://sandbox.evernote.com/edam/note/<shardId>



sylphide_ffr31mr at 23:09コメント(0)トラックバック(0) 

2010年12月16日

わがまま言って、妻から早くもらっちゃいました。
クリスマス&誕生日プレゼント。


さっそく開けちゃう。
じゃじゃ~ん。

右側はお財布。左側は、"あの象"がついているメモ帳。
詳しくは、以下の商品説明を見てもらったほうがわかりやすい。

もらってから、一週間ほど使っているが、実に使い勝手がいい。
simple is a bestというか、最小限の機能美とでもいうか。

まず、財布。
商品説明にあるとおり、カードの類は5枚しか入らない。
いままでは、あちこちの店のポイントカードや、スタンプカードやら、20枚くらい突っ込んであったが、頻出するものだけに絞ると、5枚で十分。
主要コンビニ3社のポイントカード、近くのドラッグストアのポイントカード、会社の行きつけのコーヒー屋さんくらい。
銀行カードや、クレジットカードは、元々別のカードケースに入れている。
あとは、思い切って、鞄にいれっぱなしのカードケースにしておいても、なんら不便はない。

あと、心配だったのは、小銭入れがとても小さいこと。
使ってみると逆に、何の小銭が入っているかが一目瞭然なので、無駄に探す時間がかからず、スムースに支払いが終わる。
そして、大事なことは、小銭が一杯になっているということは、金額の端数が支払える確率が上がるので、小銭の数が増えないように端数を出すことができる。
結果、一杯になって入りきれない、という状態には、なりにくい。
それでも、入りきれなくなったら、ポケットにじゃらじゃらでもいいと思うが、今のところそういう状態にはなったことがない。

そして、Evernoteとタイアップしたメモ帳。
商品紹介にあるとおり、A4用紙の1/8サイズにぴったしな大きさ。蓋の部分には、カードが一枚入るので、通勤定期のPASMOを入れている。
このPASMO、半年に二回もIC部分が壊れてしまった。今までは、お尻のポケットに入れていたからか、と思い、このメモ帳と共に胸ポケットに引っ越しです。
さて、仕事中はシステム手帳を持ち歩き、メモはそこにいつもしていた。手帳を持っていないときは、iPhoneで自分にメールするとかもしていた。
紙は、できるだけ使わなくする、そう考えていたわけだが、スキャンしてEvernoteに保存する、という発想がなかったからだ。
結構、電車に乗っている時や、歩いているときにも、人はいろいろ考える。
その時、ふと、メモしたいとき、iPhoneでは、
  • ポケットから取り出す
  • ロックを解除する
  • メモアプリがあるページにスクロール
  • メモアプリの起動
  • (場合によっては)新規ノートの作成
といった手数が必要になる。
紙なら、その半分だ。
しかも、細かく書けるし、線も自由自在だ。字は下手なので、読みにくいが。
このすばやさ、は、頭から逃げないうちに、とか、面倒がらないようにとか、メリットが大きい。
しばらくは、試行錯誤も続くだろうが、使い続けたい!と思えるアイテムである。

iPhoneを持った時、使うことが楽しいと感じた。その楽しさが便利さと共にあることで、何年も使い続けるモチベーションとなる。
abrAsusのこの2つの製品は、同じ種類のときめきを与えてくれた。
そして、最初にも書いたが、シンプルであること。それがこの製品の、最大のよさだと思う。



sylphide_ffr31mr at 21:21コメント(0)トラックバック(0) 

2010年05月25日

Evernoteから簡単にノートを取り出すスクリプトをバージョンアップした。
動作環境の準備については、前のエントリを参照していただくとして。

使い方は、以下のとおり。
キーワードを1つ引数で指定し、そのキーワードにマッチするノートのリストを得る。 そして、最初にヒットしたノートの本文を表示する。
キーワードと工夫し、よく参照するノート固有のキーワードをタグなどでつけておけば、webやクライアントを起動することなくノートの内容を簡単に参照できる。

$ ruby note.rb KEYWORD

以下のオプションを追加した。

-gノートの識別子GUIDを直接してしてノートを取り出す。GUIDは、キーワードサーチしたときのノートリストにタイトルと共に表示している。
-rノートを表示するときに、
タグを外したり、アンパサンドや不等号マークなどを変換して表示しているが、このオプションを付けると、そうした変換をせず、そのまま表示する。

-gオプションをつけて、GUIDを直接指定する場合は、以下のようになる。

$ ruby note.rb -g aaaaa-bbbbb-cccccc-ddddd-eeeeeeeee

ソースコード

note.rb
#!/usr/bin/ruby
#
# Evernote Simple note searcher
# ver.1.4 2010-05-25 Yasuki
#
require 'rubygems'
require 'optparse'
require 'pp'
require "thrift"
require "Evernote/EDAM/user_store"
require "Evernote/EDAM/user_store_constants.rb"
require "Evernote/EDAM/note_store"
require "Evernote/EDAM/limits_constants.rb"

#
# 引数チェック
if (ARGV.size==0)
puts "Usase: note.rb <SearchWord>"
puts "Usase: note.rb -g <GUID>"
exit 1
end

#
# 初期値
guid=""
raw=0
username="LOGINNAME"
password="LOGINPASSWORD"
consumerKey = "CONSUMERKEY"
consumerSecret = "CONSUMERSECRETKEY"
userStoreUrl = "https://sandbox.evernote.com/edam/user"
noteStoreUrlBase = "http://sandbox.evernote.com/edam/note/"
#userStoreUrl = "https://www.evernote.com/edam/user"
#noteStoreUrlBase = "http://www.evernote.com/edam/note/"

userStoreTransport = Thrift::HTTPClientTransport.new(userStoreUrl)
userStoreProtocol = Thrift::BinaryProtocol.new(userStoreTransport)
userStore = Evernote::EDAM::UserStore::UserStore::Client.new(userStoreProtocol)

#
# 認証
authResult = userStore.authenticate(username, password,
consumerKey, consumerSecret)
user = authResult.user
authToken = authResult.authenticationToken
puts "Authentication was successful for #{user.username}"

#
# ノート取得準備
noteStoreUrl = noteStoreUrlBase + user.shardId
noteStoreTransport = Thrift::HTTPClientTransport.new(noteStoreUrl)
noteStoreProtocol = Thrift::BinaryProtocol.new(noteStoreTransport)
noteStore = Evernote::EDAM::NoteStore::NoteStore::Client.new(noteStoreProtocol)

#
# オプション解析
opt=OptionParser.new
opt.banner="This program is easy evernote searcher."
opt.version="1.4"

opt.on("-g GUID","--guid", "note id.") { |v|
guid = v.to_s
}
opt.on("-r","--raw", "Don't replace specific charactors") { |v|
raw = 1
}
opt.on_tail("-h", "--help", "Show this message.") { |v|
puts opt
exit 1
}
opt.on_tail("-v", "--version", "Show version.") { |v|
puts opt.version
exit 1
}
searchword=opt.parse!(ARGV).to_s

# ノートの検索
if searchword.size >= 1 then
search = Evernote::EDAM::NoteStore::NoteFilter.new
search.words = searchword
res = noteStore.findNotes(authToken, search, 0, 100)
# 検索リストの表示
puts "------------------------"
puts "Find note: " + searchword
puts "------------------------"
res.notes.each do |note|
printf("%s %s\n", note.title, note.guid)
end
guid=res.notes[0].guid
end

# ノート本文の取得
noteEdam=noteStore.getNote(authToken, guid, 1,0,0,0)

#
# ノートの表示
puts "------------------------"
puts noteEdam.title
puts "------------------------"
c=noteEdam.content
bodyHead="<en-note>"
bodyEnd="</en-note>"
body=c[ c.index(bodyHead)+bodyHead.length .. c.index(bodyEnd)-1 ]
if raw==0 then
brTag='<br clear="none"/>'
body.gsub!(brTag,"\n")
body.gsub!("<div>","")
body.gsub!("</div>","")
body.gsub!("&quot;",'"')
body.gsub!("&lt;","<")
body.gsub!("&gt;",">")
end
puts body


sylphide_ffr31mr at 21:48コメント(0)トラックバック(0) 

2010年05月15日

EvernoteのAPI KEYが届いたので、サンプルプログラムをまねして、APIをいじってみた。
API KEYは、申請から7時間後に届いた。(夕方に申請したので、米国が活動時間だったと思う)

1.準備

(1)API KEYの申請

以下のフォームに必要事項を記入して申し込む。

http://www.evernote.com/about/developer/api/

メールで、consumer keyとconsumer secretが送られてくるので、それをプログラムに埋め込む。
最初の段階では、実験用システム(sandbox)にしか登録されないので、デバッグ等をそのシステムで行う。
本番システムのAPI KEYをもらうには、再度申請が必要。その場合も、同じフォームでよい、と思う。案内のメールには、本番用の申請方法は書いてなかった。
sandboxのシステムには、本番と同じくwebアクセスをして、自分でuser IDとパスワードを再登録しないといけない。
ノートも空っぽなので、実験用のノートも新たに作っておかなければならない。

(2)Evernote APIのライブラリをダウンロードする。

$ cd ~/ruby
$ wget http://evernote.s3.amazonaws.com/api/evernote-api-1.15.zip
$ unzip evernote-api-1.15.zip

(3)展開したライブラリにPATHを通す

ふたつのパスを設定することに注意。

$ export RUBYLIB=$RUBYLIB:~ruby/evernote-api-1.15/lib/ruby:~/ruby/evernote-api-1.15/lib/ruby/Evernote/EDAM

2.サンプルプログラム

サンプルプログラムは、以下を使った。というか、rubyはこれしかない。
認証系は、ほぼサンプルのまま。

~/ruby/evernote-api-1.15/sample/ruby/EDAMTest.rb

3.作成するプログラム機能

キーワードを1つ引数で指定し、そのキーワードにマッチするノートのリストを得る。
そして、最初にヒットしたノートの本文を表示する。
キーワードと工夫し、よく参照するノート固有のキーワードをタグなどでつけておけば、webやクライアントを起動することなくノートの内容を簡単に参照できる。

$ ruby note.rb KEYWORD

4.ソースコード


最新バージョンはこちら


note.rb
#!/usr/bin/ruby
#
# Evernote Simple note searcher
# ver.1.1 2010-05-14 Yasuki
#
require 'rubygems'
require 'pp'
require "thrift"
require "Evernote/EDAM/user_store"
require "Evernote/EDAM/user_store_constants.rb"
require "Evernote/EDAM/note_store"
require "Evernote/EDAM/limits_constants.rb"

#
# 初期値
username="LOGINNAME"
password="LOGINPASSWORD"
consumerKey = "CONSUMERKEY"
consumerSecret = "CONSUMERSECRETKEY"
userStoreUrl = "https://sandbox.evernote.com/edam/user"
noteStoreUrlBase = "http://sandbox.evernote.com/edam/note/"

userStoreTransport = Thrift::HTTPClientTransport.new(userStoreUrl)
userStoreProtocol = Thrift::BinaryProtocol.new(userStoreTransport)
userStore = Evernote::EDAM::UserStore::UserStore::Client.new(userStoreProtocol)

#
# Version Check
versionOK = userStore.checkVersion("Ruby EDAMTest",
Evernote::EDAM::UserStore::EDAM_VERSION_MAJOR,
Evernote::EDAM::UserStore::EDAM_VERSION_MINOR)
if (!versionOK)
puts "Is my EDAM protocol version up to date? #{versionOK}"
exit(1)
end

#
# 認証
authResult = userStore.authenticate(username, password,
consumerKey, consumerSecret)
user = authResult.user
authToken = authResult.authenticationToken
puts "Authentication was successful for #{user.username}"
#puts "Authentication token = #{authToken}"

noteStoreUrl =
noteStoreUrlBase + user.shardId
noteStoreTransport = Thrift::HTTPClientTransport.new(noteStoreUrl)
noteStoreProtocol = Thrift::BinaryProtocol.new(noteStoreTransport)
noteStore = Evernote::EDAM::NoteStore::NoteStore::Client.new(noteStoreProtocol)

#
# ノートの検索
search = Evernote::EDAM::NoteStore::NoteFilter.new
search.words = ARGV[0]
res = noteStore.findNotes(authToken, search, 0, 100)

#
# ノートリストの表示
puts "------------------------"
puts "Find note: " + ARGV[0]
puts "------------------------"
res.notes.each do |note|
printf("%s %s\n", note.title, note.guid)
end

#
# ひとつめのノートの表示
bodyHead="<en-note>"
bodyEnd="</en-note>"
brTag='<br clear="none"/>'

guid=res.notes[0].guid
noteEdam=noteStore.getNote(authToken, guid, 1,0,0,0)

puts "------------------------"
puts noteEdam.title
puts "------------------------"
c=noteEdam.content
# puts c
# puts "=============================="
body=c[ c.index(bodyHead)+bodyHead.length .. c.index(bodyEnd)-1 ]
body.gsub!(brTag,"\n")
body.gsub!("<div>","")
body.gsub!("</div>","")
body.gsub!("&quot;",'"')
body.gsub!("&lt;","<")
body.gsub!("&gt;",">")
puts body


5.出力例

$ ruby note.rb tv
Authentication was successful for LOGINNAME
------------------------
Find note: tv
------------------------
TV予約 aaaaa-bbbbb-cccccc-ddddd-eeeeeeeee

------------------------
TV予約
------------------------
[テレビ予約リスト]
ハートキャッチプリキュア 日  8:30 テレビ朝日
花咲ける青少年           日 23:00 NHK BS2
ふしぎ大好き    月  9:00 NHK教育
ドラえもん      金 19:00 テレビ朝日

[ 放送終了]
シュガーバーニーズ 金  8:00 kidsチャネル
とある科学                 土 1:30 MX
キディーガーランド   土 2:30 MX
ささめきこと    木  2:20 テレビ東京
涼宮ハルヒの憂鬱  土  2:30 Tokyo MX
グインサーガ    日 23:29 NHK BS2
ジパング      木 22:30 アニマックス
戦場のヴァルキリア 火 14:00 アニマックス
鉄のラインバレル  水 00:00 Kidsチャネル
電脳コイル     火 20:00 NHK BS2
東京マグニチュード 金  0:45 フジテレビ
夏目友人帳     月 17:30 テレビ東京
亡念のサムド 水 23:30 MX
Monster 金 2:00 Animax
東のエデン 金 00:45 フジ
デスノート   火   23:00 アニマックス
エウレカ 木 23:00 キッズステーション



sylphide_ffr31mr at 20:30コメント(0)トラックバック(0) 
記事検索
最新コメント
livedoor プロフィール
月別アーカイブ
  • ライブドアブログ