きっと3日坊主

Every day is day 3.

Python2.7からGmailを送信する

研究室で毎週特定の曜日(水曜日)の特定の時間にお知らせメールを送信する仕事を任されていて、いままでは前日の毎週火曜日にRight Inboxを使って送信設定していたが面倒になった + Right Inboxの無料枠を使い潰したくないので、Gmailを送信するPythonスクリプトを作ってそれをcronから叩くようにした。備忘録として手順を残しておきます。

Google API Client Libraryを使って、PythonからGmailを送信

今回は、Google API Client Libraryという各言語から各種APIにアクセスする用のライブラリを使った方法を書いていきます。

Gmailを送信するまでの手順

GmailPythonから送信するまでには、OAuthの認証周りを含む以下のような手順が必要です。

  1. Google Developer Consoleから新しいプロジェクトを作成し、OAuthクライアントIDを取得する
  2. authorization codeを取得する
  3. authorization codeをアクセストークンとリフレッシュトークンと交換する
  4. メールを送信する

新しいプロジェクトを作成し、OAuthクライアントIDを取得する

Google Developer Consoleから新しいプロジェクトを作ったら、API Managerの認証情報 -> 認証情報を作成 -> OAuthクライアントID を選択し、クライアントIDとクライアントシークレットを作成します。

f:id:thunders1028:20170605195438p:plain

次に出てくる「アプリケーションの種類」の部分では、今回はPythonスクリプトからAPIを叩くので「その他」を選択してください。

これで、クライアントIDとクライアントシークレットを取得することができました。

次に、Pythonからプロジェクトの情報を利用するための設定ファイル client_secrets.json を作成します。

{
  "web": {
    "client_id": "クライアントID",
    "client_secret": "クライアントシークレット",
    "redirect_uris": [],
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://accounts.google.com/o/oauth2/token"
  }
}

使い捨てみたいなスクリプトをつくるんならわざわざファイルを分けずにハードコードしてもいいんじゃない?って思うかもしれないけれど、OAuth認証をするライブラリ oauth2client がファイルから情報を読むようになっているので、ファイルに書いておいたほうが無難です。(あとでコードを公開することになったときにヘタをこくのも防止できる)

authorization codeを取得する

ここからは、Python上の作業になります(すこしブラウザ操作も入りますが…)。

authorization codeとは、アクセストークンとリフレッシュトークン(後述)を取得するために一度だけ使うトークンのようなものです。一度アクセストークンを取得するために使い終わると、そのauthorization codeはもう使うことができません。

def retrieve_authorize_code(flow):
    auth_url = flow.step1_get_authorize_url()
    webbrowser.open(auth_url) # ブラウザで認証画面を表示
    exit(0)

~~~

"""Main"""
flow = client.flow_from_clientsecrets(
    "client_secrets.json",
    scope = "https://www.googleapis.com/auth/gmail.send", # メール送信の権限を取得する
    redirect_uri = "urn:ietf:wg:oauth:2.0:oob") # authorization codeをブラウザ上に表示

retrieve_authorize_code(flow)

flowとは、OAuthの処理の流れを制御するオブジェクト…のようなものです。OAuthとflowについては、Google Client LibraryのOAuthのガイドページにあるスライドがとても簡潔にまとめられていてわかりやすいです。

さて、retrieve_authorize_code(flow) を実行すると、ブラウザが立ち上がり認証画面が表示されます。

f:id:thunders1028:20170605201537p:plain

ここで「許可」をクリックすると、画面上にauthorization codeが表示されます。

f:id:thunders1028:20170605201738p:plain

このcodeをコピーしてとっておきましょう。

実行したコードは、webbrowser.open(auth_url) のところで止まっています。Ctrl-cで一度中断してください。codeを取得してコード上に埋め込んだら(次のステップでやります)、retrieve_authorize_code(flow) はもう不要です。コメントアウトするなりしましょう。

authorization codeをアクセストークンとリフレッシュトークンと交換する

取得したauthorization codeを使って、アクセストークンとリフレッシュトークンを交換します。リフレッシュトークンとは、アクセストークンの期限が切れた際に、新しいアクセストークンを取得するのに使われるトークンです。

def retrieve_credential(flow):
    auth_code = "auth_code" 
    credential = flow.step2_exchange(auth_code)

    #with open("credential.pickle", "w") as fpickle:
    #    pickle.dump(credential, fpickle)

    #with open("credential.pickle", "r") as fpickle:
    #   credential = pickle.load(fpickle)

    return credential

auth_code のところにauthorization codeを書いてください。

flow.step2_exchange(auth_code) にauthorization codeを渡すだけでアクセストークンとリフレッシュトークンが入ったcredentialが取得できます。このcredentialはメール送信をする際に使いまわすので、DBなどに保存してください。今回は簡単にpickleで保存しました。(oauth2clientのStorage.locked_put() というのを使うのがスジらしいけどなぜか自分の環境ではうまく動かなかったのでこの方法になりました。)

メールを送信する

やっとメール送信です。

def create_service(credential):
    http_auth = credential.authorize(httplib2.Http())
    return build("gmail", "v1", http=http_auth)

def create_mail(message_text, subject, to, from_):
    message = MIMEText(message_text)
    message["subject"] = subject
    message["to"] = to
    message["from"] = from_

    return {"raw": base64.urlsafe_b64encode(message.as_string())}

def send_mail(service, raw):
    try:
        message = service.users().messages().send(userId="me", body=raw).execute()
        print "A message sent. Id: {}".format(message["id"])

    except errors.HttpError, error:
        print "An error occurred: {}".format(error)

~~~

flow = client.flow_from_clientsecrets(
    "client_secrets.json",
    scope = "https://www.googleapis.com/auth/gmail.send",
    redirect_uri = "urn:ietf:wg:oauth:2.0:oob")

credential = retrieve_credential(flow)

service = create_service(credential)

message_text = "hello, world!"
subject = "test subject"
to = "example@gmail.com"
from_ = "example@gmail.com"

mail = create_mail(message_text, subject, to, from_)

send_mail(service, mail)

疲れたので続きはあとで追記します…