golangとGmail APIで大量の未読メールを既読にする

きっかけ

メールを3年ぶりに整理しようとしたところ、未読メールが20万件ほど溜まっていたので適当に検索して出てきた未読メールの一括既読を試してみたのですが、どうも件数が多すぎてエラーを吐かれているらしい

ブラウザから一括既読する方法についての記事

news.livedoor.com

発生したエラー

f:id:kaneta1011:20180209165943p:plain

上記の方法でも100件ずつすれば問題ないのですが2000回も反復作業はできないので、gmailAPIを利用して分割して既読にすることにしました

今回作成したコードはこちら

github.com

動作環境

~$ go version
go version go1.8.3 linux/amd64

OAuth認証

googleAPIにアクセスするためには認証をしないといけないのでこちらのクイックスタートを見て認証を終えた(認証部分やアクセストークンの保存等の実装は丸まる拝借した)

https://developers.google.com/gmail/api/quickstart/go

後はマニュアルを見つつ、既読にするコードに変更していく

godoc.org

未読メールを100件取得する

メールを取得するメソッドはこちら

https://godoc.org/google.golang.org/api/gmail/v1#UsersMessagesService.List

ただし、上記のメソッドをそのまま利用すると未読既読関係なく最新のメールを100件取得してしまうのでクエリパラメータを加える、それが以下のメソッド

https://godoc.org/google.golang.org/api/gmail/v1#UsersMessagesListCall.Q

上記のメソッドに文字列でブラウザのgmail上で利用できる検索ボックスと同じものを指定して取得するメールを限定できる、今回は未読メールなので is:unread を指定しました

以上を踏まえて、未読100件のメールを取得するコードは以下のようになる

mes, err := srv.Users.Messages.List(user).Q("is:unread").Do()
if err != nil {
    log.Fatalf("Error: %v", err)
}

ちなみに、取得時に件数が0の場合はエラーになる

メールを既読にする

APIのスコープを変更する

未読メールには UNREAD というラベルが設定されているので、そのラベルをメールから削除して既読にする

チュートリアルのサンプルではAPIのスコープがReadOnlyになっているので、スコープを gmail.GmailModifyScope に変更しました

config, err := google.ConfigFromJSON(b, gmail.GmailModifyScope)
if err != nil {
    log.Fatalf("Unable to parse client secret file to config: %v", err)
}

一度に100件既読にする

メールの状態を変更するメソッドは ModifyBatchModify があるが、今回は100件一気に変更したいので BatchModify を利用する

https://godoc.org/google.golang.org/api/gmail/v1#UsersMessagesService.Modify

https://godoc.org/google.golang.org/api/gmail/v1#UsersMessagesService.BatchModify

先程取得した100件のメールからIDのスライスを作成して UNREAD ラベルを削除するリクエストの構造体を作成後に実行すれば未読100件の既読が達成できる

ids := []string{}
for _, e := range mes.Messages {
    fmt.Println(e.Id)
    ids = append(ids, e.Id)
}

request := &gmail.BatchModifyMessagesRequest{
    Ids:             ids,
    AddLabelIds:     []string{},
    RemoveLabelIds:  []string{"UNREAD"},
    ForceSendFields: []string{},
    NullFields:      []string{},
}
err = srv.Users.Messages.BatchModify(user, request).Do()
if err != nil {
    log.Fatalf("Error: %v", err)
}

未読0件になるまで繰り返す

実は100件の未読メールを取得する際に、取得件数が0の場合はエラーを返すようになっているので、上記の流れをループすればOK

3秒毎に100件ずつ既読にしているので結構時間がかかりますが一日寝かせてみます