imapsyncでモバメをGmailにコピーする その3 - ローカルのmboxをThunderbird + Got Your BackでGmailにコピーする

現在受信しているモバメはimapsyncで問題なくGmailに同期できるようになった。

喜んでいたのもつかの間、昔ローカルにコピーを取っていたモバメのmboxファイルを発見してしまった。ついでにこれもGmailに同期しておきたい。

mboxをimapにコピーするツールは探せばいくらでも出てくるが、Gmailに特化したGot Your Back(GYB)を使うのが確実そう、アップデートもマメだしということで使ってみた。

Home · jay0lee/got-your-back Wiki · GitHub

最近GmailがIFTTTなどのサードパーティーアプリを使用禁止にしたという話題があって、このGYBも影響を受けたんだけど

自分のアカウントでプロジェクトを作ってOAuth認証すればよいという回避策を取ったようです。

Google is stopping acces to gyb - Google Groups
Release GYB 1.20 · jay0lee/got-your-back · GitHub

うーん、それでいいのだろうかという気もする(笑)パスワード忘れそうだから紙に書いて貼っておくわ!に通じるものがあるような・・・。

まあいいけど。

使い方はWikiの方にめっちゃ丁寧に書いてあるので、それを読んでそのままやれば問題ない。Cygwinのコンソール(mintty)だと標準入力でハングするのでコマンドプロンプトを使った。

オプションはこんな感じで。

C:gyb>gyb --email kobayashi01234@gmail.com --action restore-mbox --local-folder local_mbox_hkt --strip-labels --label-restored Mobame/Mobame_HKT48

Using backup folder local_mbox_hkt

Restoring from 1.36gb file local_mbox_hkt\hkt.mbox...
restoring 1 messages (14920)
done!

C:gyb>gyb --email kobayashi01234@gmail.com --action restore-mbox --local-folder local_mbox_mobame --strip-labels --label-restored Mobame

Using backup folder local_mbox_mobame

Restoring from 42.65mb file local_mbox_mobame\mobame.mbox...
 restoring 5 messages (745) - 100.00%
Restoring from 17.45mb file local_mbox_mobame\namamail.mbox...
 restoring 5 messages (630) - 100.00%
done!

ちなみに"gmail mbox インポート"などで検索するとThunderbirdのImportExportToolsというアドオンを使ってローカルにインポートして、IMAP4アカウントのフォルダにコピーするという手順がたくさん出てくる。

仕事がはかどるGmailテクニック(14) アーカイブ機能で作成したバックアップをGmailにインポートするには | マイナビニュース

これでもまぁよい。

ただメールの数が少ない(1000未満くらい)と問題なく動くんだけど、メールの数が多い(1万オーバー)といつの間にか止まっていたりしてうまく動かないので注意。

同期したら Remove Duplicate Messages (Alternate) というアドオンで重複したメールのチェックができるので、同じメールをコピーしていないかチェックする。

GYBの方でも重複メールをコピーしない機能がデフォルトでONになっているから、基本的には問題ないと思う。

imapsyncでモバメをGmailにコピーする その2

特に実用上支障はない(自分は使わないので)けど、日本語のフォルダが文字化けする。
これはデコードにUnicode::Stringを使ってるせいで、Encode::IMAPUTF7を使うとうまくいく。
IMAP4ではUTF-7ではなく修正UTF-7というものを使っているのが原因らしいが、まあ詳しく調べる気はあんまりない。

use strict;
use warnings;

use Unicode::String;
use Encode;
use Encode::IMAPUTF7;

my $s = shift;
print imap_utf7_decode($s);
print "\n";
print encode('utf8', imap_utf7_decode2($s));

sub imap_utf7_decode2 {
  my $s = shift;
  return decode('IMAP-UTF-7', $s);
}

# Copied from http://cpansearch.perl.org/src/FABPOT/Unicode-IMAPUtf7-2.01/lib/Unicode/IMAPUtf7.pm
# and then fixed with
# https://rt.cpan.org/Public/Bug/Display.html?id=11172
sub imap_utf7_decode {
        my ( $s ) = shift ;

        # Algorithm
        # On remplace , par / dans les BASE 64 (, entre & et -)
        # On remplace les &, non suivi d'un - par +
        # On remplace les &- par &
        $s =~ s/&([^,&\-]*),([^,\-&]*)\-/&$1\/$2\-/xg ;
        $s =~ s/&(?!\-)/\+/xg ;
        $s =~ s/&\-/&/xg ;
        return( Unicode::String::utf7( $s )->utf8 ) ;
}
$ perl u7.pl "&,wM- 90&ZeVO5VJNMG5T10,hMOEw,DDr-"
# 90日以前の受,hMOEw,DDr-
# 90日以前の受信メール

それにしてもimapsync、しょうもないバグが多くないか。重要なバグがないから良いという考え方もあるが。

imap_utf7_encode()も同じように直したほうがいいかもしれない(未確認)

imapsyncでモバメをGmailにコピーする

この話の続き。

ThunderbirdでドコモメールをGmailに自動転送する - kkobayashi_a’s blog

今までThunderbirdのフィルタ機能を使ってキャリアメールからGmailにモバメをコピーしていたけど、そのためだけに常時Thunderbirdを起動させておくのがどうもオーバースペックだなと思っていて。メモリ300~500MBくらい使ってるし。

近々Windowsを再インストールするので、せっかくだからThunderbirdを使わずコマンドラインでcronに登録できるようなものを探してみました。

どうやらimapsyncというツールで目的が達成できそう。

Official imapsync migration tool ( release 1.921 )

有料のソフトっぽいけど、GitHubで古いバージョンが公開されている。

GitHub - imapsync/imapsync: Imapsync is an IMAP transfers tool. The purpose of imapsync is to migrate IMAP accounts or to backup IMAP accounts. IMAP is one of the three current standard protocols to access mailboxes, the two other are POP3 and HTTP with webmails, webmails are often tied to an IMAP server. Upstream website is

makeの仕方がよく分からん。まあ中身はPerlスクリプトなのでそのまま実行できますね。

オプションはこんな感じ。

imapsync --host1 $HOST1 --user1 $USER1 --password1 $PASS1 --user2 $USER2 --password2 $PASS2 --gmail2 --folder INBOX --f1f2 INBOX=Mobame --nofoldersizes --nofoldersizesatend --noresyncflags --search "SENTSINCE 06-Apr-2019 OR FROM mls.ngt48.com FROM sp.hkt48.jp"

自分はGmailで"Mobame"ラベルを付けているので、--f1f2 INBOX=Mobame の設定でInboxのメールをMobameフォルダ(IMAP上ではラベルはフォルダ扱いになる)にコピーする。

--nofoldersizes --nofoldersizesatend は時短のため。

--noresyncflags はGmail側で既読にしたメールが次回のsyncで未読に戻ってしまうのを防ぐため。

--searchオプション、IMAPのsearch queryがポーランド記法らしく癖がある。モバメのアドレスを増やす場合は "OR OR FROM mls.ngt48.com FROM sp.hkt48.jp FROM mm.akb48.co.jp"という感じで書けばよい(と思う)。

おまけ(Cygwinで動かす場合)

Cygwinで動かすとpsのオプションがなんちゃらというエラーが出る。これはツール自身のメモリー使用量を "ps -o vsz" で取得しているため。

Thunderbirdを常駐させておくことに比べたらメモリー使用量なんて微々たるものなのでエラーなど無視しておいてもいいんだけど、せっかくなので直しておく。

https://github.com/imapsync/imapsync/pull/22

WindowsだけじゃなくてCygwinの場合もtasklistからメモリー使用量を取得するようにする。

実はここの修正だけでは不十分で、memory_consumption_of_pids_win32()関数内でCygwinのPID -> WindowsのPIDの変換をする必要があります。

Perlで書かれてると治すのが楽でいいわ。笑

sub memory_consumption_of_pids_win32 {
        # Windows
        my @PID = @_;
        if ( 'cygwin' eq $OSNAME ) {
                @PID = map { open my $fh, '<', "/proc/$_/winpid"; $_=<$fh>; tr/\x0A\x0D//d; $_ } @PID;
        }

スキャンしたファイルをHuginでstitchする

雑誌のスキャンはドキュメントスキャナよりフラットベッドスキャナの方が圧倒的に使いやすい。

スキャン画像に変な白い線や黒い線が入ってイライラすることもないし、硬い紙も柔らかい紙も気にせずスキャンできるし、複数ページの巻き込みもない。

買う前には気づかなかったメリットとしては、ポスターやA4ワイド雑誌など、一度でスキャンできないものも複数の画像をつなぎ合わせて(stitch)きれいにスキャンできる。

今まではスキャナに付いてたScan-n-Stitch Deluxeというのを使っていて、全く不満はなかったのだけど、ファイルが増えてくるといちいちポチポチやるのが面倒くさい。バッチで一気に処理したい。

と思って調べていると、Huginというパノラマ画像作成ツールがコマンドラインツールをサポートしてるらしい。というか、Hugin自体がコマンドのフロントエンド(GUI)になってるっぽい。

Hugin tutorial — Stitching flat scanned images

チュートリアルにやり方が載っているのでそのままやれば良い。

・・・はずなのだけど、全然うまくいかん。たぶん高機能すぎて使い方が分からないんだと思う。そもそもカメラを前提にしてるので、設定項目も意味がわからん。

で、試行錯誤の末、超簡単なやり方を見つけた。

f:id:kkobayashi_a:20190318012550p:plain

アシスタントの画面から、"1. Load Images" で画像を読み込み。FOVは10を設定すれば良い。次に"2. Align"を実行。そして"3. Create panorama"で画像を生成。

・・・普通にアシスタントに沿っていれば問題なかった。チュートリアルとはなんだったのか。

親切なことにチュートリアルのページからシェルスクリプトをダウンロードすることができる。これを実行すると期待通りコマンドから一連の処理ができる。

現在のバージョン(2018.0.0)で動くように少し手を加えたけど、概ね元のスクリプトで動くっぽい。

#! /bin/sh
# hugin command tools script to stitch scanned images, fov unknown
# use of fov >= 10 should be OK, could simply set FOV=10
# Terry Duell 2013, 2014

# usage...run-scan-pto_var.sh outputprefix fov

#get the output file prefix
Prefix="$1"

# get the fov
FOV=10

pto_gen --projection=0 --fov=$FOV -o project.pto "$2" "$3"
pto_lensstack -o project1.pto --new-lens i1 project.pto
cpfind -o project1.pto --multirow project1.pto
cpclean -o project2.pto project1.pto
linefind -o project3.pto project2.pto
pto_var -o setoptim.pto --opt 'r,d,e,!r0,!d0,!e0' project3.pto
autooptimiser -n -o autoptim.pto setoptim.pto
pano_modify  --projection=0 --fov=AUTO --center --canvas=AUTO --crop=AUTO --ldr-file=JPG --ldr-compression=95 -o autoptim2.pto autoptim.pto
hugin_executor --stitching --prefix=$Prefix autoptim2.pto

# pto2mk -o project.mk -p $Prefix autoptim2.pto
# make -j 2 -f project.mk all

# open pto files from each step in hugin to check how it all works

古いバージョンではpto2mk & makeでやっていた処理が、今ではhugin_executorで一気にできるようになったんですね。makeをインストールしてない環境でも簡単に実行できるのはよい。

なお3枚以上の画像を結合する場合はpto_lensstackで画像ごとに新しいレンズを割り当てるようにすればよいのだと思う(未確認)。

opencvでスキャン後の余白カット

ImageMagickの-trimオプションでもいいかなあと思っていたんだけど、斜めにスキャンした画像の補正とか-fuzzの調整とか色々面倒。

と思いつつImageMagickを使ったりGIMPの台形補正(遠近法)ツールでシコシコやったりしてごまかしていたけど、どうやらopencvを使うとわりと簡単にできそうなのでやってみた。

最近は便利そうなライブラリはみんなPythonバインディングなのね。Perlがほしい。

「四角」「矩形検出」「python」「opencv」みたいなワードで検索するとたくさん情報やサンプルコードが出てくるので参考になります。

導入

チュートリアルのページなどを見つつ必要なモジュールをインストールする。

昔は違ったらしいけど、今はpipで簡単にインストールできる。

$ python -m pip install cv2
$ python -m pip install numpy
$ python -m pip install matplotlib

Pythonopencvは内部の数値計算にnumpyを使っていて、内部データがnumpyになっている。

numpyの使い方を覚えておくともっと効率よくプログラミングできそう。

matplotlibは特に必須ではないけど、画像やグラフを表示するときに使う。

あとチュートリアルのページに出てくるのでサンプルコードを動かすためにも入れておいたほうがよい。

コード

検索してみると、だいたいの流れは

  1. 画像の2値化
  2. 輪郭検出
  3. 台形補正

という感じ。

輪郭検出に使う画像が2値化画像なので、前もって画像を2値化しておく必要がある。

このやり方によって輪郭検出の精度がだいぶ変わってくるので、ここの調節が重要。

輪郭検出で四角形が検出できたら、その4頂点を使って台形補正を行えば余白削除&斜行補正のされた画像が得られる。

import sys
import numpy as np
import cv2
from matplotlib import pyplot as plt

def find_contours(img):
    image, contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    areas = []
    for cnt in contours:
        area = cv2.contourArea(cnt)
        if area > img.size*0.5 and area < img.size*0.95:
            epsilon = 0.1*cv2.arcLength(cnt, True)
            approx = cv2.approxPolyDP(cnt, epsilon, True)
            if len(approx) >= 4:
                areas.append(approx)

    img_c = cv2.drawContours(cv2.cvtColor(img, cv2.COLOR_GRAY2BGR), areas, -1, (0,0,255), 3)
    return img_c, areas

img = cv2.imread(sys.argv[1])
print("shape : " + str(img.shape))
print("size  : " + str(img.size))
print("dtype : " + str(img.dtype))

# statistics of margin
# H: 180*0.658 = 118.44, sigma=3.6
# S: 255*0.164 = 41.82, sigma=7.9
# V: 255*0.192 = 48.96, sigma=9.43

img_org = img
img     = cv2.GaussianBlur(img, (5, 5), 0)
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
for n in range(30, 120, 5):
    mask_margin = cv2.inRange(img_hsv, (100,0,0), (120,0+n,0+n))
    r, area_margin = find_contours(mask_margin)
    print("find_contours :", n, "->", len(area_margin), "found")
    if area_margin:
        break

if not area_margin:
    print ("error: no contours found")
    sys.exit(0)

## sort contours topleft,topright,bottomleft,bottomright

area = area_margin[0]
area = sorted(area, key = lambda x: (x[0][0]+x[0][1]))  # x+y (x^2+y^2 is better?)
if area[1][0][0] < area[2][0][0]:
    tmp = area[1];
    area[1] = area[2]
    area[2] = tmp

print("contour:", area)

# 出力座標の計算(三平方の定理)

l_top = area[0][0]
r_top = area[1][0]
l_btm = area[2][0]
r_btm = area[3][0]

top_line   = (abs(r_top[0] - l_top[0]) ^ 2) + (abs(r_top[1] - l_top[1]) ^ 2)
btm_line   = (abs(r_btm[0] - l_btm[0]) ^ 2) + (abs(r_btm[1] - l_btm[1]) ^ 2)
left_line  = (abs(l_top[0] - l_btm[0]) ^ 2) + (abs(l_top[1] - l_btm[1]) ^ 2)
right_line = (abs(r_top[0] - r_btm[0]) ^ 2) + (abs(r_top[1] - r_btm[1]) ^ 2)
max_x = top_line  if top_line  > btm_line   else btm_line
max_y = left_line if left_line > right_line else right_line

print("width={}, height={}".format(max_x, max_y))

# 画像の座標上から4角を切り出す
pts1 = np.float32(area)
pts2 = np.float32([[0, 0], [max_x, 0], [0, max_y], [max_x, max_y]])

# 透視変換の行列を求める
M = cv2.getPerspectiveTransform(pts1, pts2)

# 変換行列を用いて画像の透視変換
img_cropped = cv2.warpPerspective(img_org, M, (max_x, max_y))

plt.subplot(1,3,1),plt.imshow(cv2.cvtColor(img_org, cv2.COLOR_BGR2RGB),'gray'),plt.title('ORIGINAL')
plt.subplot(1,3,2),plt.imshow(cv2.cvtColor(r, cv2.COLOR_BGR2RGB),'gray'),plt.title('mask')
plt.subplot(1,3,3),plt.imshow(cv2.cvtColor(img_cropped, cv2.COLOR_BGR2RGB),'gray'),plt.title('cropped')
plt.show()

結果


斜めにスキャンされた画像もキレイに余白が削除されてますね。

細かい話

解説というかメモ。

ガウシアンぼかし
img     = cv2.GaussianBlur(img, (5, 5), 0)

最初にガウシアンぼかしをかけて色を滑らかにしておくと後の処理がしやすい。
(5, 5)はカーネルサイズ。でかい画像はカーネルサイズを大きくするなど調節が必要そう。

2値化画像
def find_contours(img):
    image, contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    areas = []
    for cnt in contours:
        area = cv2.contourArea(cnt)
        if area > img.size*0.5 and area < img.size*0.95:
            epsilon = 0.1*cv2.arcLength(cnt, True)
            approx = cv2.approxPolyDP(cnt, epsilon, True)
            if len(approx) >= 4:
                areas.append(approx)

    img_c = cv2.drawContours(cv2.cvtColor(img, cv2.COLOR_GRAY2BGR), areas, -1, (0,0,255), 3)
    return img_c, areas

img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
for n in range(30, 120, 5):
    mask_margin = cv2.inRange(img_hsv, (100,0,0), (120,0+n,0+n))
    r, area_margin = find_contours(mask_margin)
    print("find_contours :", n, "->", len(area_margin), "found")
    if area_margin:
        break

多くのサイトではcv2.thresholdで画像を2値化する方法を紹介しているけど、今回の画像では今ひとつ上手くいかない。

普通に2値化しただけだと全く検出できない。

2値化のチューニング案としては、例えば色々な色空間のチャネルごとに輪郭検出をしてマスク画像を合成するというのがある。

確かに精度は良くはなったけど、名刺やポストイットみたいな単色の抽出ならともかく雑誌のページみたいな複雑な画像は難しいみたい。

上手くチャネル分解するとか、ガウシアンぼかしのチューニングをするとか、色々調整が必要っぽい。

面倒になったので、今回の目的に特化して「スキャナーの余白を削除する」を素直に実装することにした。

OpenCVのinRange()関数で画像が特定の色範囲に含まれているかどうかチェックできる。

この結果を2値画像として輪郭検出処理にかけることにする。

またRGB(OpenCVはBGRだけど)よりHSVの方が色検出に向いてるようなので、HSVに変換してからinRange()で余白を抽出する。

GIMPで余白部分の情報を取得して、パラメーターの参考にする。

# statistics of margin
# H: 180*0.658 = 118.44, sigma=3.6
# S: 255*0.164 = 41.82, sigma=7.9
# V: 255*0.192 = 48.96, sigma=9.43

なるほど。(100,0,0), (120,50,50) くらいにすれば良さそう。

この値も検出精度に関わるので、下限から少しずつ上げていって輪郭検出ができた時点で止めるというロジックにした。

色々試した結果、今回のデータだと大体S,V = 60くらいで検出できるようになる。

findContours()で輪郭の検出、approxPolyDP()で輪郭の多角形近似ができる。

検出した輪郭のうち、小さすぎたり大きすぎたり(画像全体を輪郭として抽出することがある)するものを除いて、近似後の頂点が4のものを四角形として保存する。

approxPolyDPのパラメーターはコピペ。。

領域(輪郭)の特徴 — OpenCV-Python Tutorials 1 documentation

epsilon = 0.1*cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,epsilon,True)
頂点のソート
area = area_margin[0]
area = sorted(area, key = lambda x: (x[0][0]+x[0][1]))  # x+y (x^2+y^2 is better?)
if area[1][0][0] < area[2][0][0]:
    area[1], area[2] = area[2], area[1]

切り取った画像のサイズ計算から射影変換の流れは以下のページをそのまま参考にした(コピペともいう)のだけど、そのまま使うと画像が回転したり反転したりする。

OpenCVで台形補正がしたかった話。応用編。

ので、あらかじめ頂点をソートする。numpyの関数でもっとスマートにできるのかもしれない。

  1. 原点からの距離が近いものが左上、遠いものが右下
  2. 残り2つはxの値が大きい方が右上、小さい方が左下

という感じで、左上、右上、左下、右下という順番に並び替える。

ところでPythonって変数のスワップをtmpみたいな一時変数なしに直接書いてOKなんですね。地味に便利。最近のLLは大体そうかな?

追記



見にくいけど、[130 55 55]のところで余白削除が不完全になってて台形でトリミングされてしまっている。

ので、領域が直角かどうかのチェックを加えることにした。

あと、あまり下の方からチェックしていくと変なのが検知されるので、もう少し大きな値からスタートすることにした。

何にしてもトリミングした後の目視確認は必要ですね・・・。

def angle(p1, p2, p3):
    p12 = p1[0] - p2[0]
    p23 = p3[0] - p2[0]

    dot = np.dot(p12, p23)
    norm_12 = np.linalg.norm(p12)
    norm_23 = np.linalg.norm(p23)
    cos = dot / (norm_12 * norm_23)
    rad = np.arccos(cos)
    theta = rad * 180 / np.pi
    return theta

def vars_from_rightangle(quad):
    # 01
    # 23
    r1 = angle(quad[0], quad[1], quad[3])
    r2 = angle(quad[1], quad[3], quad[2])
    r3 = angle(quad[3], quad[2], quad[0])
    r4 = angle(quad[2], quad[0], quad[1])
    var = 0
    for r in (r1, r2, r3, r4):
        var = var + pow((90-r), 2)
    print("r1={}, r2={}, r3={}, r4={}, var={}".format(r1, r2, r3, r4, var))
    return var
find_contours : [100   0   0] [130  50  50] -> 0 found
find_contours : [100   0   0] [130  55  55] -> 1 found
r1=92.19912728111747, r2=88.25982795073007, r3=89.18250765828346, r4=90.35853710986905, var=8.661202147533869
find_contours : [100   0   0] [130  60  60] -> 1 found
r1=89.5558975296218, r2=90.29483478368576, r3=89.81778811349452, r4=90.33147957319791, var=0.4272344328984021

EvernoteのAPIを使ってみる

Documentation - Evernote Developers

Perlでいいかなぁと思ったけど公式ドキュメントが不親切なのでPythonにする。

Evernote Cloud API — Python Quick-start Guide - Evernote Developers

getting startedのドキュメントによると、まずは

  1. sendboxのアカウントを取得する
  2. Evernote API keyの申請をする

次にSDKをインストールする。

$ pip install evernote

を実行するとエラーになる・・・

GitHub - evernote/evernote-sdk-python: Evernote SDK for Python
GitHub - evernote/evernote-sdk-python3: Testing the Evernote Cloud API for Python 3

This is a test SDK! The official Evernote SDK for Python doesn't support Python 3 yet; this repository is an experiment as we try to migrate.

えぇ・・・。

今の時代はPython3だと聞いたのでPython2とか入れてないんだけど。

http://dogwood.skr.jp/blog/2013/12/426/

なるほどね〜

というわけでCygwinPythonをアンインストールしてWindows版にする。

$ py -2 -m pip install evernote

うまくいった。

一通り環境はできたのでサンプルコードを書きたい。が、その前にアクセストークンを取得しておく。

Developer Tokens - Evernote Developers

sandboxのdeveloper tokenはすぐに取得できるけど、productionのdeveloper tokenは "Update: the creation of developer tokens is temporarily disabled." などというメッセージが出て取得できないようになってる。

仕方がないのでsupportのページからAPI keyのactivation申請をする

Support - Evernote Developers

に行くと"Activate an API Key"というボタンがあるので適当に入力する。

How do I copy my API key from Sandbox to www (production)?


This process is called key activation. To activate your API key on Evernote’s production servers, file a key activation request using this form. Once we’ve determined that your integration fulfills all of the requirements of a production application, your key will be activated within 1?2 business days. You’ll be notified when your key has been activated (or if there’s an issue with your application that needs to be addressed).

申請項目のpermissionというのがよく分からんかったけど、多分ここのページに乗ってる意味でのpermissionなんだと思う。

API Key Permissions - Evernote Developers

とりあえず "Full access" permissionを下さいな、と書いておいた。

サンプルコード

ノートブックとノートブック内のノートのリストを表示する。

from evernote.api.client import EvernoteClient
from evernote.edam.notestore.ttypes import NoteFilter, NotesMetadataResultSpec

dev_token = "<your developer token>"
client = EvernoteClient(token=dev_token)

userStore = client.get_user_store()
user = userStore.getUser()
print "username : " + user.username + "\n"

offset = 0
max_notes = 10

noteStore = client.get_note_store()
notebooks = noteStore.listNotebooks()

for n in notebooks:
	print "* " + n.name
	filter = NoteFilter(notebookGuid=n.guid)
	result_spec = NotesMetadataResultSpec(includeTitle=True)
	result_list = noteStore.findNotesMetadata(dev_token, filter, offset, max_notes, result_spec)
	for note in result_list.notes:
		print "\t- %s" % note.title
$ py -2 a.py
username : kobayashi01234

* <Inbox>
* プロジェクトその1
* testbook1
        - testnote001

なるほどね。

シンプルにノートブック名を指定してノート一覧を取得できればいいのだけど、そういう機能はないっぽい。

Get a notebook by name and its notes - Evernote API Discussion - Evernote User Forum

追記

API keyのactivation mailが6/3朝(USだとすると現地時間では6/2夕方)くらいに来ていた。結構すぐに来るものなんだな。そして適当に申請してもだいじょうぶそうw

CygwinでHTTPS接続できないときはca-certificatesをチェックしよう

何度かブログで触れている気がするdropbox-apiスクリプト

https://github.com/s-aska/dropbox-api-command

いつの頃からか(今思えばCygwinパッケージをアップグレードした辺りからか・・・)エラーになった。

$ dropbox-api ls /sync
2018-05-28T16:51:48 [WebService::Dropbox] [ERROR] https://api.dropboxapi.com/2/files/list_folder {"path":"/sync"} -> [500] Can't connect to api.dropboxapi.com:443

なんやねん、dropboxが落ちてるのかAPIが終了になったのか・・・と思ってcurlでチェックしてみるとAPIは問題なく動作してるっぽい。

$ curl -k -X POST https://api.dropboxapi.com/2/files/list_folder \
>     --header "Authorization: Bearer $ACCESS_TOKEN" \
>     --header "Content-Type: application/json" \
>     --data "{\"path\": \"/sync\"}"

ちなみに-kをつけないとcertificateのエラーが出る(これも伏線)。

curl: (77) error setting certificate verify locations:
  CAfile: /etc/pki/tls/certs/ca-bundle.crt
  CApath: none

小一時間悩んで、そもそもPerlHTTPSアクセスができないのでは・・・と思って簡単なスクリプトでチェックしてみたらビンゴっぽい。

use strict;
use warnings;
use LWP::UserAgent;
use HTTP::Request;

my $ua = LWP::UserAgent->new;
my $req = HTTP::Request->new( GET => 'https://www.flickr.com/');
my $response = $ua->request($req);

### $response
### $response: bless( {
###                     _content => 'Can\'t connect to www.flickr.com:443',
..
###                     _msg => 'Can\'t connect to www.flickr.com:443',
###                     _rc => 500,

PerlというかLWPでHTTPSのコンテンツが取得できない、ってわりと良くある割に分かりにくい問題で、一昔前ならNet::SSLeayをインストールしましょうみたいなのがテンプレだったのだけど。

PerlでSSL(https)のサイトのコンテンツが取得できない - ノウハウブログ - カンタローCGI

一通り必要そうなモジュールをチェックしてみたけど導入済みだし、何やろうなあ。

"perl https 500 error"で検索したら最初に出てくるサイト。

perl - 500 error with LWP:UserAgent - Stack Overflow

"verify_hostname=0" ねえ・・・そういや上のcurlでも証明書のエラーが出てたな、と思って試してみたら動くではないか。

というわけで、当初の問題は環境変数 PERL_LWP_SSL_VERIFY_HOSTNAME=0 を指定すれば解決。

$ PERL_LWP_SSL_VERIFY_HOSTNAME=0 dropbox-api ls /sync

・・・解決はするけど、毎度毎度証明書を無視するオプションを付けるのは面倒だしセキュリティ的にもいかがなものかと思うので、もう少し調べる。

と、Cygwinでは ca-certificates パッケージが証明書ファイルを持っているようなので再インストールしてみると環境変数を指定しなくても動くようになった。

よかったよかった。それにしてもHTTPSというかSSLというか証明書周りは面倒くさいなあ・・・。