Python の標準ライブラリで POP3 を操ってみる

これは [ユニキャストアドベントカレンダー](https://qiita.com/advent-calendar/2017/unicast) の１日目の記事です。

個人的に滅びて欲しいけどどうにもならない技術TOPにランクインしているのが電子メールです。
早くチャットに置き換わって欲しいと常日頃感じているのですが、業務上使わざるを得ないのが実情です。
（ちなみにユニキャストでは Chatwork で基本的に社内コミュニケーションを取っています。非エンジニアにも馴染みやすいUIなのがよいですね。）

# poplib で迷惑メールを処理する

電子メールのつらさを助長している存在の一つとして、SPAMメールがあります。
「ものすごい量の迷惑メールが来てしまってメールがパンクしてます！助けて！」というヘルプが来ることがまれに良くあります。
Battery Included の思想を掲げる Pythonなら標準ライブラリの一部である [poplib](https://docs.python.jp/3/library/poplib.html) を使って POP3 で簡単にメールボックス内のメールの迷惑メールの削除処理をすることができます。

## POP3 の仕組み

POP3 (Post Office Protocol v3) はリモートのメールボックスからメールを受信するためのプロトコルです。
POP3プロトコルはメールサーバ上の電子メールの受信、削除、およびそれらを実行するユーザの認証の手順を定めており、ほぼすべてのメールソフトやメールサーバがサポートしているプロトコルです。

POP3はシンプルなテキストプロトコルなので、telnet や netcat でも比較的簡単に使うことが出来ます。
TLSによる暗号化もサポートしていますが、本稿では簡単のためすべて平文にて例示します。

以下は netcat で POP3 を話す例です。

```shell-session
[jsaito@localhost ~] $ netcat pop.example.com 110
+OK Dovecot ready.                                                                                                                                                                              
USER jsaito@example.com                                                                                                                                                                    
+OK                                                                                                                                                                                             
PASS password
+OK Logged in.                                                                                                                                                                                  
LIST                                                                                                                                                                                          
+OK 102 messages:
1 614923
2 2487
～中略～
.
RETR 1
～中略～
.
DELE 1
+OK Marked to be deleted.
QUIT
+OK Logging out, messages deleted.
```

以下の操作をしています。

- `USER` コマンドと `PASS` コマンドでユーザ認証
- メールの一覧を `LIST` コマンドで取得
- `RETR` コマンドで1番のメールを受信
- `DELE` コマンドで1番のメールに削除フラグを立てる
- `QUIT` コマンドで削除フラグを立てたメールの削除を実行し、セッションを切断

QUITコマンドを実行せずに接続を切断した場合は、DELEコマンドで指定したメールは削除されません。

## Python で POP3 するサンプル

poplib は POP3 のコマンドとメソッドが1対1で実装されているので、POP3 の仕組みがわかっていれば、雰囲気でやっていけます。
以下は送信者（Fromヘッダ）に特定の文字列、例えば迷惑メールの送信者が含まれていた場合に `DELE` コマンドで削除するスクリプトです。
ヘッダだけ受信して処理するため、普通にメールソフトで受信して削除するよりも高速にメールを削除することができます。

```python3
#!/usr/bin/python3

import poplib
import email
import email.header

# サーバに接続
cli = poplib.POP3('mail.example.com')
print('connected.')

# 認証
cli.user('jsaito@example.com')
cli.pass_('password')
print('logged in.')

# メールボックス内のメールの総数を取得
count = len(cli.list()[1])
print('found ' + str(count) + ' messages.')

deleted = 0
try:
    for i in range(count):
        no = i + 1
        # TOPコマンドでヘッダだけ受信する
        content = cli.top(no, 0)[1]
        msg = email.message_from_bytes(b'\r\n'.join(content))
        from_ = str(msg['From'])
        # 送信者（Fromヘッダ）に特定の文字列が含まれていたらメールを削除
        if from_.find('qq.com') > 0 or from_.find('163.com') > 0 or from_.find('sina.com') > 0:
            print('From: ' + from_ + ' => DELE ' + str(deleted))
            cli.dele(no)
            deleted += 1
finally:
    cli.quit() # 最後に必ず QUIT する
print(str(deleted) + ' messages deleted.')
```

`quit()` は例外がスローされても必ず呼ばれるようにしておきます。
でないと、ものすごく大量のメール削除を走らせていて途中でエラーになった場合、QUITで削除が実行されず、最初からやり直しになってしまいます。

# まとめ

POP3 プロトコルを Python の標準ライブラリの poplib で処理するサンプルを例示しました。
Python は近頃の Linux ディストロなら必ずインストールされているので、このような保守運用をサポートするプログラムをさくっと書けるのでよいですね。

以上、よろしくお願いします。