Pythonでメール受信; poplibのサンプルコード

# 関連リンク
- [Pythonでメール送信; smtplibのサンプルコード](https://qiita.com/takey/items/6e45a203f1c055a9024c)
- [Pythonでメール受信; imaplibのサンプルコード](https://qiita.com/takey/items/1498af9e1113eeb7bb21)

# 処理の流れ
1. POP3クライアントインスタンスを作成する
2. POP3サーバにログインする
3. メール受信（MIMEメッセージを受信）

# サンプルコード
```python
import base64
import email
import poplib
import ssl
from email.header import decode_header, make_header
# https://docs.python.jp/3/library/poplib.html

"""
(1) POP3クライアントインスタンスを作成する
* Yahoo!メールの場合
  host: "pop.mail.yahoo.co.jp"
  nego_combo: ("ssl", 995)
              （2021年1月のYahooメールのセキュリティ強化につき110ポートが廃止）
              
* Gmailの場合
  host: "pop.gmail.com"
  nego_combo: ("ssl", 995)
"""
host = "pop.gmail.com"
nego_combo = ("ssl", 995) # ("通信方式", port番号)

if nego_combo[0] == "no-encrypt":
	popclient = poplib.POP3(host, nego_combo[1], timeout=10)
elif nego_combo[0] == "starttls":
	context = ssl.create_default_context()
	popclient = poplib.POP3(host, nego_combo[1], timeout=10)
	popclient.stls(context)
elif nego_combo[0] == "ssl":
	context = ssl.create_default_context()
	# context = ssl.SSLContext(ssl.PROTOCOL_TLSv1) うまくいかない場合はこちらも。
	popclient = poplib.POP3_SSL(host, nego_combo[1], timeout=10, context=context)
popclient.set_debuglevel(2)  # サーバとの通信のやりとりを出力してくれる


"""
(2) POP3サーバにログインする
"""
username = "username@gmail.com"
password = "password"
auth_method = "user"

if auth_method == "user":
	popclient.user(username)
	popclient.pass_(password)
elif auth_method == "apop":
	# Gmailはnot supported
	# yahooはnot supported
	popclient.apop(username, password)
elif auth_method == "rpop":
	# Gmailはできない
	# yahooはできない
	popclient.rpop(username)
	popclient.pass_(password)


"""
(3) メール受信（MIMEメッセージを受信）
"""
download_num = 3 # ダウンロードしたい数
msg_list = [] # 取得したMIMEメッセージを格納するリスト
msg_num = popclient.stat()[0]  # POP3サーバに存在するメールの数を取得
if msg_num <= download_num:
	download_num = msg_num
for i in range(download_num):
	msg_bytes = b""
	for line in popclient.retr(i+1)[1]:  # i+1から始まる（参考：https://docs.python.org/ja/3/library/poplib.html#:~:text=for%20j%20in%20M.retr(i%2B1)%5B1%5D%3A）
		msg_bytes += line + b"\n"
	msg_list.append(email.message_from_bytes(msg_bytes))
popclient.quit()


"""
受信したメール（MIMEメッセージ）の確認
"""
for msg in msg_list:
	print(msg)
	print()
	
"""
各ヘッダや本文を取得する
"""
for msg in msg_list:
	# 各ヘッダはディクショナリのようにアクセスできる
	from_addr = str(make_header(decode_header(msg["From"])))
	subject = str(make_header(decode_header(msg["Subject"])))
	print("From:{}".format(from_addr))
	print("Subject:{}".format(subject))

	# 本文(payload)を取得する
	if msg.is_multipart() is False:
		# SinglePartのとき
		payload = msg.get_payload(decode=True) # 備考の※1
		charset = msg.get_content_charset() # 備考の※2
		if charset is not None:
			payload = payload.decode(charset, "ignore")
		print(payload)
		print()
	else:
		# MultiPartのとき
		for part in msg.walk():
			payload = part.get_payload(decode=True)
			if payload is None:
				continue
			charset = part.get_content_charset()
			if charset is not None:
				payload = payload.decode(charset, "ignore")
			print(payload)
			print()


"""備考
※1 get_payload(decode=True)について
    cte = msg["Content-Transfer-Encoding"]や
    cte = part["Content-Transfer-Encoding"]
    でContent-Transfer-Encodingヘッダを取得してデコード方式を決定しても良いが、
    get_payload()のdecode引数をTrueにすると、
    Content-Transfer-Encodingヘッダに従って勝手にデコードしてくれる。
    ただしデコードしてくれるのはquoted-printable または base64のときのみで、
    それ以外の場合（別のエンコード方式や、ヘッダがない場合、ペイロード部が
    bogus dataを持っている場合 (i.e. bogus base64 or uuencoded data)）のときはそのまま返す。
    * bogus dataって何だ？
      - MAILER-DAEMON@yahoo.co.jpのメールがデコードしきれずにpayloadがバイト列になったので、
        そういうのを指すのかもしれない。

※2 get_content_charset()について
    charsetがない不親切なメールがたまにあるので注意。
    気合でデコードするしかないが、今回のサンプルコードはそのようなメールは考慮していない。
"""
```