【Python】アイコンのダブルクリックだけでメールを自動送信できるようにしてみた

# あらすじ

定常的なメールの送信作業が発生しており、面倒なので自動化することにしました。

自動化にあたり、以下要件を満たすものを作ることにしました。

　１．特定のファイルを自動的に添付したい
　２．メールに添付するファイルは同時にGoogle Driveにもアップしたい
　３．メール送信時のみオンラインにし、送信後はすぐにオフラインにしたい

そこで、上記要件を満たすツールをPythonで作ろうと思いました。

上記3つの要件が本当に実現化なのか、まず最初に検討しました。


## 要件1: 特定のファイルを自動的に添付したい

Pythonでメール送信するためのパッケージimaplibは当然ファイル添付も可能であるためと分かったため、これを使うことに。

## 要件2: メールに添付するファイルは同時にGoogle Driveにもアップしたい

これも調べてみたところ、PyDriveというPythonからGoogle Driveを操作するパッケージがあると分かったので、それを使うことにしました。Python、便利すぎるぞ。。

## 要件3: メール送信時のみオンラインにし、送信後は即座にオフラインにしたい

PythonならWi-Fiの接続/切断を簡単に操作できるパッケージぐらいあるでしょ…と軽く考えて調べてみましたが、残念ながら見つかりませんでした。

しかし、PowserShellであればWi-Fiの操作が簡単にできること、またPowerShellスクリプトもPython側から容易に呼び出せることが分かったため、Wi-Fi操作はPowerShellで実施することに。


# 実施結果

作成したPythonスクリプトとPowerShellスクリプトを適切なところに置き、.pyのショートカットをデスクトップに置いてそれを叩けば自動メール送信が実行できます。参考までに、実施結果は以下のようになりました。


![キャプチャ.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271833/faa39fe0-6868-9ad7-e61f-b55d0c58e33e.jpeg)



# ソース

１．Python：メイン部分

```python:auto_mail_tool.py
import os
import time
import email
import imaplib
import smtplib
import datetime
from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
from os.path import basename
from email.header import Header
from email.utils import formatdate
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication


# wifiへの接続/切断
def wifi(mode):

    if mode == 'connect':  # 指定したSSIDのwifiに接続（SSID自体はPowerShell内で設定）
        os.system('powershell -Command' + ' ' + \
                  'powershell -ExecutionPolicy RemoteSigned .\\wifi_on.ps1')

        time.sleep(5)

    elif mode == 'disconnect':  # 接続中のwifiから切断

        os.system('powershell -Command' + ' ' + \
                  'powershell -ExecutionPolicy RemoteSigned .\\wifi_off.ps1')


# 送信メールに添付したいファイルをGoogle Driveにアップロード
def up_file_on_drive():

    print('ファイルをGoogle Driveにアップロードしています。')

    # アップロード対象のファイルがあるパス及びファイル名を設定
    tgtfolder = 'ファイルのパス（ファイル名は含まず）'
    tgtfile = 'アップロードしたいファイルの名前'
    dlttgt = 'title = ' + '"' + tgtfile + '"'  # ファイルIDの取得に使用

    # Google Drive APIを使用するための認証処理
    gauth = GoogleAuth()
    drive = GoogleDrive(gauth)

    # 既に同名のファイルが格納されている場合、削除
    file_id = drive.ListFile({'q': dlttgt}).GetList()[0]['id']
    f = drive.CreateFile({'id': file_id})
    f.Delete()

    time.sleep(3)

    # ファイルをアップロード
    folder_id = '1kCwhAtoPIi2TAUK0wiAr9ecYifyve7TW'
    f = drive.CreateFile({'title': tgtfile,
                         'mimeType': 'excel/xlsx',
                         'parents': [{'kind': 'drive#fileLink', 'id': folder_id}]})
    f.SetContentFile(tgtfolder + tgtfile)
    f.Upload()

    print('ファイルのアップロードが完了しました。')


# メールを添付ファイル付きで送信
def send_mail_with_file():

    st = get_mail_settings()  # 送信メールの内容を取得

    # 送信するメールの内容を設定
    msg = MIMEMultipart()
    msg["Subject"] = st['subject']
    msg["To"] = st['to_addr']
    msg["From"] = u'%s<%s>' % (str(Header(st['send_name'].encode(st['charset']), st['charset'])), st['from_addr'])
    msg['Bcc'] = st['bcc_addr']
    msg['Date'] = formatdate()
    msg.attach(MIMEText(st['body']))

    # ファイルを添付
    with open(st['file'], "rb") as f:
        part = MIMEApplication(f.read(), Name=basename(st['file']))
    part['attached_file'] = 'attachment; filename="%s"' % basename(st['file'])
    msg.attach(part)

    # メールを送信
    smtpobj = smtplib.SMTP_SSL('smtp.gmail.com', 465, timeout=10)  # GmailのSMTPサーバのポート番号を指定
    smtpobj.login(st['from_addr'], st['mypass'])
    smtpobj.send_message(msg=msg)
    smtpobj.close()


# メールの設定内容を取得
def get_mail_settings():
    info = {
        'charset': 'iso-2022-jp',
        'send_name': u'送り主の名前',
        'from_addr': '送り主のアドレス',
        'mypass': 'Gmailアカウントのパスワード',
        'bcc_addr': '送り先１, 送り先２',
        'to_addr': '送り先１, 送り先２',
        'subject': 'メールの件名 ' + get_today(),
        'body': 'メール本文',
        'file': '添付したいファイルのパス + ファイル名'
    }
    return info


# メールが送信されたか確認
def confirm_mail_sent():

    print('メールを送信しています。')

    st = get_mail_settings()  # 送信メールの内容を取得

    tgtac = imaplib.IMAP4_SSL('imap.gmail.com', 993)  # gmail受信メールサーバ（IMAP）のホスト名とSSLを用いたメール受信のポート番号
    tgtac.login(st['from_addr'], st['mypass'])

    waitsec = 10  # メール送信確認ロジックのタイムアウト時間[sec]

    # 自動送信したメールが受信されているか、1秒おきに最新メールから確認
    for i in range(waitsec, 0, -1):

        lamtitle = get_latest_mail_title(tgtac)  # 最新メールの件名を取得

        time.sleep(1)  # 1ループ1秒

        if lamtitle == st['subject']:  # もし最新メールの件名が自動送信したメールのものだったら
            print('\nメールの自動送信が完了しました。\n')
            return

    # 確認のための時間がタイムアウトした場合
    print('\nメールの自動送信に失敗しました。\n')


# 最新メールの件名を取得
def get_latest_mail_title(mail):
    mail.select('inbox')  # メールボックスの選択
    data = mail.search(None, 'ALL')[1]  # メールボックス内にあるすべてのデータを取得
    tgt = data[0].split()[-1]  # 最新メールの順番を取得
    x = mail.fetch(tgt, 'RFC822')[1]  # メールの情報を取得（Gmailで読取可能な規格を指定）
    ms = email.message_from_string(x[0][1].decode('iso-2022-jp'))  # パースして取得

    sb = email.header.decode_header(ms.get('Subject'))
    ms_code = sb[0][1]  # 文字コード取得

    # 最新メールの件名のみを取得
    if ms_code != None:
        mtitle = sb[0][0].decode(ms_code)
    else:
        mtitle = sb[0][0]

    return mtitle


# 本日の日付を取得
def get_today():
    now = datetime.date.today()
    tdy = str(now.year) + '/' + str(now.month) + '/' + str(now.day)  # 年月日で表示
    wknum = now.weekday()  # 本日の曜日番号を取得（0:月 ... 6:日）
    wk = get_now_weekday(wknum)  # 本日の曜日を取得

    return tdy + '(' + wk + ')'


# 本日の曜日を取得
def get_now_weekday(key):
    wkdict = {0: '月', 1: '火', 2: '水', 3: '木', 4: '金', 5: '土', 6: '日'}
    return (wkdict[key])


if __name__ == '__main__':

    wifi('connect')        # 1. 指定したSSIDのwifiを接続
    up_file_on_drive()     # 2. 添付したいファイルを一旦GoogleDriveにアップ
    send_mail_with_file()  # 3. ファイルを添付してメール送信
    confirm_mail_sent()    # 4. GASによって送信されたメールが届いているか確認
    wifi('disconnect')     # 5. 指定したSSIDのwifiを接続

    os.system('PAUSE')     # コンソールを止める

```

２．PowerShell：Wi-Fi接続/切断用

２－１．接続時

```powershell:wifi_on.ps1
netsh wlan connect name="接続したいWi-FiのSSID"
```

２－２．切断時

```powershell:wifi_off.ps1
netsh wlan disconnect
```

# 備考：MATLABから.pyを呼び出したい場合

system関数を用いて.m に以下のように記述します。

```matlab
system('実行したい.pyのパス')
```


# 参考にさせていただいた情報

特に下記サイト様にお世話になりました。ありがとうございます。

|内容|リンク先|
|---|---|
|PyDriveでGoogleドライブにファイルをアップロード|https://note.nkmk.me/python-pydrive-download-upload-delete/ https://qiita.com/akabei/items/f25e4f79dd7c2f754f0e|
|PythonからPowerShellスクリプト（.ps1）の呼び出し|https://tkstock.site/2019/10/07/python-powershell-shellscript-activate/|
|PowerShellによるwifiへの接続・切断|https://qiita.com/mindwood/items/22e0895473578c4e0c7e  http://wgg.hatenablog.jp/entry/20161111/1478846489|
|Google Drive上にあるファイルの削除|https://note.nkmk.me/python-pydrive-download-upload-delete/|
|imaplibでGmailの受信メール情報を取得|https://py.minitukuc.com/2017/11/07/gmailhonbun/|
|Tkinterダイアログ使用時のルートウィンドウ非表示|https://stackoverflow.com/questions/1406145/how-do-i-get-rid-of-python-tkinter-root-window|
|PythonでGmail送信|https://qiita.com/nakasuke_/items/607cf74d8841f76e59c6|
|PythonでGmail送信時にファイル添付|https://time-space.kddi.com/ict-keywords/kaisetsu/20170824/2081|
|PythonでGmail送信時に送信元の名前を設定|https://teratail.com/questions/128993|


# 最後

ご指摘・改善案・間違い指摘、大歓迎です。どしどしいただけると、喜びに咽びます。