メインコンテンツへスキップ
見出し画像

【解説】PythonでGmail受信&自動化ツール

    こんにちはRcatです。
    前回投稿した以下のGmailを受信して、Pythonで好きに料理するツールの内部解説を行います!


    はじめに

    利用規約

    情報や作品の活用時は事前に利用規約をご確認ください。

    コメントについて

    利用規約のガイドラインを確認の上コメントしてください。
    則っていないコメントは削除します。


    構成

    本ツールは次のような構成で作成されています。

    画像

    メインのスクリプトはgmail.pyです。
    他のスクリプトから参照するため、メールを保持するクラスに関しては別に分けておりそれがemailmessage.pyとなります。
    メールを受信した際の自動処理はmodulesフォルダーの中にアドインのような形でテンプレートを複製して作成します。
    こうすることでカスタマイズ性を向上させています。

    また、本作は自動環境構築に対応しており、WindowsとLinuxでそれぞれ起動スクリプトを実行することで、環境構築から起動まで自動で行うことができます。

    なお、今回のツールはAI駆動開発を用いており、大枠は生成AIを使って叩き台を作った後、正常に動かない部分や細かいところを修正していくというやり方で制作しています。

    スクリプトメイン

    では、メインのスクリプトの解説を行います。

    実行部とメインループ

    こちらがスクリプトが起動した時に実行される部分となります。
    グローバル領域で設定ファイルを読み込んだ後、設定がなければ終了という形にしています。
    設定を読み込めた場合はアドインという形で、この後紹介するカスタム処理のスクリプトを読み込んだ後、メインループに入ります。

    画像

    メインループの中では無限ループを行い、定期的にチェックを行います。
    メールの受信とそれを使った処理に関しては別の関数にまとめられており、この中では、ただループと最終取得日時の更新を行っています。

    メールの受信処理

    メールの受信部分が最も長い関数となります。こちらはAIを使って生成したものを動かしながら、実態に合わない部分を私が修正するといった形で作成した部分となります。

    今回は標準ライブラリだけでメールの受信を行うためメールの送受信プロトコルであるIMAPに則った通信を行うかなり低レベルな処理となっています。

    画像

    この辺の処理については、この辺りが参考になります。

    https://atmarkit.itmedia.co.jp/fnetwork/rensai/netpro09/imap4-fetch.html
    https://atmarkit.itmedia.co.jp/ait/articles/0204/10/news001.html

    この関数は新しく送られてきたメールを受信し、この後紹介する。Eメールオブジェクトに内容を格納します。
    メールは複数送られてくる可能性があるので、Eメールオブジェクトのリストをリターンします。

    まず最初にメールの検索です。
    ここでは日付指定でそれ以降のメールしか検索できないため、大まかな検索を行います。
    例えば5分おきにチェックしていたとしても、5分前の時刻からではなく今日のメール全てをチェックすることになります。

    画像

    今日送られてきたメールの一覧(ID)を取得するとこの先の処理に進みます。
    なお、5分おきなどかなり短い間隔でチェックを繰り返すため、ここの時点で前回取得したメールの件数を保持しておきます。
    こうすることで、件数が同じなら新しいメールがないという判断を行い、余計な処理をせずに済みます。

    各メールの詳細の受信とフラグ

    メールの一覧を受信したら、次にメールの詳細を取得します。
    ここでは各メールのIDを使ってループを回すことで古い順から1つ1つメールをチェックしていきます。

    まず最初にフラグの確認です。
    これは既読なのか未読なのかの確認になります。この後行う本文の受信を行うと、強制的に開封済みになってしまいます。
    スマートフォンでもチェックしている場合、自動処理でメールが勝手に既読になると、今日の新しいメールが何なのかわかんなくなってしまいますよね。そうしないため、未読だった場合は受信後に未読に戻すという処理を行うためにフラグを取得します。
    なお、本来であればメールを開封せずに受信するという方法(RFC822.PEEK)があるはずなんですが、そのリクエストを送るとエラーになってしまったので、今回はこのような対応をとっています。

    画像

    次に時刻の取得です。
    そのメールの受信時刻を取得します。この時点で前回チェックしたメールよりも古いメールであれば、これは処理しなくていいということで、この先の処理をスキップします。

    画像

    ちなみにフラグと時刻の取得はAIが作ってくれたコードと実際のメールサーバーの応答があってなかったので最初は全然動かず、悩んだところでもあります。
    何せ、メールサーバーから直接メールを取得するなんて今回初めてやりましたからね…pdbで中身見ながら何度か回して修正しましたとさ。

    メールの本文や件名の取得そして未読に戻す

    処理対象のメールであった場合、ここから先の処理が実行されます。
    まず、最初にメール本体を取得します。取得したメールはバイト列のようなので、ライブラリを使ってデコードしています。この辺りは完全にお任せです。

    画像

    差出人のデコード

    ここはちょっとポイントだったりします。
    最近はアドレスではなく名前でメールが届きませんか?それは両方のデータが含まれているからだそうですが、片方しかない場合もあるのでちゃんと処理しないと差出人が何になるか分からない。
    ここではライブラリの力できれいにデコードして分離しています。

    画像

    メールのデコード

    最後に本体のデコードです。
    特にマルチパートの場合は、本文がテキストかHTMLか、添付はあるのかなどといった様々な処理をしています。
    なので一応種類の判別と添付のバイナリを取得することができます。

    画像

    処理が終わったら最後にオリジナルのメールオブジェクトに入れて終了!

    メールオブジェクト

    今回受信したメールは極力簡単に扱えるように専用のオブジェクトに格納しています。

    こちらのクラスがその格納先となります。

    画像

    保持情報

    このクラスは以下の情報を保持します

    • UID

    • 差出人アドレス

    • 差出人名(あれば)

    • 件名

    • 本文

    • 本文タイプ

    • 時刻

    • 添付ファイル

    機能

    Save
    保持しているメールをテキスト形式で保存する機能です。
    時刻と件名がファイル名となり保存されます。一応、LinuxとWindowsで禁則となる文字は置き換える処理が入っています。
    ちなみに強制的に下記Get_Plain_Bodyと同等の状態で保存されます。

    Get_Plain_Body
    HTMLタグの除去を行なったテキストを返します。
    HTML形式のメール、例えばnoteのメールとかに関しては、HTMLタグやメタデータが9割以上を占めているため、テキストとして参照した場合、全く持って意味がわからないものになっています。そのため単純なテキストとして扱いやすいように。HTMLタグ関連は全て削除します。

    受信したメールの自動処理

    次にメールを自動処理に持っていく部分を見ていきましょう。
    この関数はメインループで実行された新規メールの問い合わせから呼び出されるもので、先ほどのメールオブジェクトのリストを受け取ります。

    画像

    ここでの処理は多くなく、まず受信したメールの保存を行い、その後、各アドインに対してメールを渡し、実行する操作があればアドインが処理を行うという流れです。

    自動処理用アドイン

    本作では、自動処理の部分は直接メインのプログラムに追記するのではなく、別のファイルを使って管理するようにしています。

    画像

    modulesフォルダの中にテンプレートが最初に入っているので、こちらを書き換えることで自由な処理を実現していきます。

    読み込み部分

    今回はimportlibを用いた動的インポートを利用することで、自動処理のファイルをフォルダにさえ入れておけば読み込めるという状態にしています。

    画像

    方法は単純で、決まった名前のフォルダの中からファイルを検索して、その名前で動的インポートをするだけ。
    そしてインポート後は決まった名前のクラスをインスタンスして返すという流れ。このクラスこそが自動処理用クラスとなります。
    メールのオブジェクトが渡され、エントリーポイントが決まっているので、そこさえば守っておけば、それ以外は自由に改変することができます。

    呼び出し設定とチェック

    まず、最初にアドインの条件チェックと設定を見ていきましょう。

    アドインの中で書き換えるべきはまずはインスタンスです。
    トリガーとなっている箇所に必要な条件を入力していきます。
    OR条件に対応できるようにリストとしています。条件がない部分は空白にしておけば無視されます。
    また、処理を停止する際にいちいちファイルを削除したり、移動したりするのも大変だと思うので、フラグを設けています。
    また、ALL = Trueにした場合はすべてのメールに対して処理を行います。

    画像

    自動処理が呼び出されると、まず最初に自分が対象なのかというチェックが入ります。
    処理は非常にシンプルです。まず、最初に送信元条件を確認し、トリガー設定があればそれが含まれるかどうかのチェックをします。
    リストを使っている関係上、ループに入った時点で全てループを回しきる=条件不一致なので、forのelse節に入った時点で対象外であると判定することができ、リターンするような構造です。同じことを件名、そして本文の順で行います。

    画像

    知識があればこの辺りのロジックも編集して、もっと複雑な判定処理にするのもいいでしょう。メインのプログラムの方では一切判定処理を持っていないので、この辺もご自由にカスタムできます。

    この処理の結果、対象のメールであると判定された場合は、カスタムアクションが呼び出されます。

    カスタム処理の記述

    やっと自動処理の記述です。
    こちらのカスタムアクションという部分に好きな処理を書いていきます。引き数で渡されているメール、オブジェクトを使い、件名や本文などを文字列として参照することができます。

    画像

    例えば、今回のテンプレートの中にはDiscordのWEBHookにメッセージを転送するための関数が埋め込まれています。

    画像

    こちらの関数ではDiscordのメッセージ上限を考慮し、2000文字に収まるような形で件名と本文を抽出して送信という形にしています。
    このように好きな処理を直接記述したり、関数として増やしたりして自動処理実現します。

    ちなみにLINEのBOT版もあります。

    画像

    WEB HOOKで、メッセージを送信しているなら、他にもメールを送信することができます。例えば、生成AIのAPIを叩いて何かの判定を自動で下して通知したりなんてこともできるかもしれませんね。
    ただ、プライベートな内容なので、やるとしてもエッジAIの方がいいでしょう。


    まとめ

    いかがでしたでしょうか?
    今回はPythonを使ったGmail受信のソースコード解説を行いました。
    最初はライブラリで簡単に受信しようかな?なんて考えていたんですが、まさかのIMAPコマンドを直に叩くということになってしまいました。ま、これはこれで勉強になったからいいんですけど。

    今まではGASを使ってGmailを受信自動化といったことをしていましたが、これだとどうしても処理に時間がかかったり、非常に複雑化していたりするのでやりづらかったんですよね。ローカルで処理できるようになり、自由度が格段に向上しました。

    何か面白い用途があれば是非行かしてみてください。
    それではまたお会いしましょう。


    リンク集

    Pythonのインストール方法

    【Linux】Systemd追加支援スクリプト

    LoggingBOTの導入方法

    PythonでDiscordのWEBHookを使用する

    ミニPCにLinuxをインストール


     
     
    モノづくりが趣味。 "売ってないなら自分で作ればいいじゃない"をコンセプトに、機械/電気/AI/プログラム/ネットワーク 全部やります。 記事執筆はAI不使用。 ご連絡や利用規約はプロフィール記事参照。

    あなたへのおすすめ