Google Keepのアカウント間データコピーをPythonで

# Googleアカウント移行

Googleのデータを別アカウントに移行するには、各サービス単位で行う必要があります。
実際に私が過去に行ったのは以下の方法となります。

|  データ  |  移行方法  |
| ---- | ---- |
|  ドライブ  |  ダウンロード＆アップロード  |
|  カレンダー  |  エクスポート＆インポート  |
|  YouTube  |  ブランドアカウントを利用し、新アカウントをユーザー追加し管理者とする  |
|  フォト  |  共有機能を利用する  |
|  メール  |  ThunderbirdなどのMUAから新旧アカウントにIMAP接続すればできそう  |
|  Keep  |  [HTML形式でダウンロード](https://support.google.com/keep/answer/10017039?hl=ja)したものを手動でコピペし、メモを作り直す  |

# Keepデータの移行をPythonで

今回またGoogleアカウントを移行することになりましたが、Keepデータが前回よりも増えたため、Pythonで自動化することにしました。

調べたところ、PythonからKeepの操作を行える[gkeepapi](https://github.com/kiwiz/gkeepapi)というライブラリがあるのでそれを利用することにします。

## gkeepapiのインストール

Anaconda環境でしたが、condaではgkeepapiライブラリがなかったため、pipでインストールしました。

```
pip install gkeepapi
```

## Pythonスクリプト

[ドキュメント](https://gkeepapi.readthedocs.io/en/latest/)を参考にして以下のスクリプトを作成しました。

```python:cpgkeep.py
import gkeepapi

# 移行元へログイン
src_keep = gkeepapi.Keep()
src_success = src_keep.login('old-account@gmail.com', 'old-password')

# 移行先へログイン
dst_keep = gkeepapi.Keep()
dst_success = dst_keep.login('new-account@gmail.com', 'new-password')

# ラベルのコピー
src_labels = src_keep.labels()
for src_label in src_labels:
    if dst_keep.findLabel(src_label.name) is None:
        # 同名のラベルが存在しないことを確認して作成
        dst_label = dst_keep.createLabel(src_label.name)
        print('created label => [' + src_label.name + ']')

# 同期
dst_keep.sync()

# メモ（ノート）のコピー
src_gnotes = src_keep.all()
for src_gnote in src_gnotes:
    # ノートの作成（タイトルの重複は可なので同名チェックは行わない）
    dst_gnote = dst_keep.createNote(src_gnote.title, src_gnote.text)
    # ピン留めのコピー
    dst_gnote.pinned = src_gnote.pinned
    # ラベルのコピー
    src_labels = src_gnote.labels.all()
    for src_label in src_labels:
        dst_label = dst_keep.findLabel(src_label.name)
        dst_gnote.labels.add(dst_label)        
    print('created note => [' + src_gnote.title + ']')

# 同期
dst_keep.sync()
```

こちらのスクリプトでできることは

- ラベルの移行
- メモデータの移行（ピン留めとラベルの情報も）

です。

動作確認した環境は、WindowsのPython3.6と3.8

## 苦労したポイント
メモにラベルを追加する処理が悩みました。
移行元のメモに設定されている（単一または複数の）ラベルを取得する必要がありますが、それを行う関数やメソッドが[ドキュメント](https://gkeepapi.readthedocs.io/en/latest/)に記載されていないのです。
諦めかかっていましたが、GitHubのモジュールの[コード](https://github.com/kiwiz/gkeepapi/blob/master/gkeepapi/node.py)をながめると、

```python:node.py
class NodeLabels(Element):
  """ 途中省略 """
  def all(self):
    """Get all labels.
    Returns:
        List[gkeepapi.node.Label]: Labels.
    """
    return [label for _, label in self._labels.items() if label is not None]
```

『Get all labels』といったコメントをallメソッド内に発見！
ということで``src_gnote.labels.all()``とallメソッドを使ってみたら、移行元のメモ（ノート）に設定されているラベルを全て取得することができました。

## 認証が失敗するケース

下記のようなエラーメッセージが出て認証に失敗する環境がありました。

```
gkeepapi.exception.LoginException: ('NeedsBrowser', 'To access your account, you
 must sign in on the web. Touch Next to start browser sign-in.')
```

原因は良くわかりませんが、該当の端末から移行元・移行先の両Googleアカウントで下記URLにブラウザでアクセスし、「次へ」ボタンを押した後に、再度Pythonスクリプトを実行すると認証が通ります。
[https://accounts.google.com/b/0/DisplayUnlockCaptcha](https://accounts.google.com/b/0/DisplayUnlockCaptcha)

但し、しばらく時間が経つとまた認証エラーが出るようになるので再度ブラウザでアクセスしてから実行する必要があります。

認証処理についてはパスワードなどハードコーディングしてるので見直ししたほうが良いかもしれません。
[resume.py](https://github.com/kiwiz/gkeepapi/blob/master/examples/resume.py)でトークンを使った認証をしているので時間があったらまた試してみたいと思います。