LoginSignup
9
5

More than 5 years have passed since last update.

Cloud Pub/Subを使ってgmailの通知を受け取るlinebot

Last updated at Posted at 2018-12-22

この記事はCAMPHOR- advent calender 2018の23日目の記事です

動機

私の所属している東洋医学研究会(通称:東医研)には、専用のgmailアカウントがありますが、ほとんどの人がメールを見ていないので、今までは受信や送信メールがあれば、人力でグループLINEに投稿していました。これをbotでなんとかしたかった。

pub/subの設定

まずは、pub/subの設定をします。この設定をすることで、gmailに何らかの変更があった場合には、pub/subを通じてpostリクエストが届きます。
Gmail APIとPub/Subでリアルタイムメール受信 on ruby[^1]の記事がとても参考になりました。この通りやったらうまくいきます
1. トピックを設定する
2. サブスクリプションを設定する
私の場合は、サブスクリプションのurlはhttps://プロジェクト名.appspot.com/pubsub/pushで受け取るようにしました

gmail apiの認証

公式のPython Quickstartを参考に進めます。上記のページに出てくるquickstart.pyを少し変えて、

store = file.Storage('token.json')
creds = store.get()
if not creds or creds.invalid:
    flow = client.flow_from_clientsecrets('credentials.json', SCOPES)
    creds = tools.run_flow(flow, store)
service = build('gmail', 'v1', http=creds.authorize(Http()))

#追加したコード
request = {
        'labelIds': ["INBOX","SENT"],
        'topicName': 'projects/******/topics/mail_for_toiken'
    }
service.users().watch(userId='me', body=request).execute()

を実行すると、これでwatchメソッドが叩けたことになります。その際、crediential.jsonが作成されて、トークンがその中に保存されます。そのため、2回目以降はブラウザで認証する必要がありません

Pub/Subから送られてくるデータを加工する

ここまでの過程がうまくいくと、GAEで、pub/subから送られるjsonを受け取れます。公式のPush Notificationのページを参考にします

受け取るjsonの例
{
    "message": {
        "data": "eyJlbWFpbEFkZHJlc3MiOiAidXNlckBleGFtcGxlLmNvbSIsICJoaXN0b3J5SWQiOiAiMTIzNDU2Nzg5MCJ9",
        "message_id": "1234567890"
    },
    "subscription": "projects/myproject/subscriptions/mysubscription"
}

"data"をbase64でデコードすると、以下のようになります

{"emailAddress": "user@example.com", "historyId": "1234567890"}

gmail apiで重要なのは、historyIdだけです。

GoogleAppEngineでgmail apiを使えるようにする。

実は、「gmail api の認証」の項で使ったコードはそのまま使用できません。GAEにアップロードしたファイルはread_only属性がつくので、認証の際にIOError(errno.EROFS, 'Read-only file system', filename)が返って来てしまいます。
oauth2client.client.GoogleCredentialsを使うとうまくいきます[^2]

example
secret = json.load(open("client_secret_for_touiken.json"))
creds = GoogleCredentials(secret["access_token"], secret["client_id"], secret["client_secret"],
                      secret["refresh_token"], secret["token_expiry"], secret["token_uri"],
                      secret["user_agent"], secret["revoke_uri"])
service = build('gmail', 'v1', http=creds.authorize(Http()), cache_discovery=False)

historyIdからmessageを取得する

参考記事[^1]にも書かれていますが、start_history_idをhistoryIdと同じにしてはいけません。私は、適当なclassを作って、前回のhistoryIdを保存するようにしました

historyIdからメッセージリストを取得
class PubSubReceiver:
    def __init__(self, start_history_id=None, history_id=None):
        self.start_history_id = start_history_id
        self.historyId = history_id

    def data(self, data):
        history_id = int(json.loads(base64.b64decode(data))["historyId"])
        if self.start_history_id is None:
            self.start_history_id = history_id - 60
        elif self.historyId < history_id:
            self.start_history_id = self.historyId
        else:
            pass
        self.historyId = history_id
        return PubSubReceiver(start_history_id=self.start_history_id,
                              history_id=history_id)

    def to_array(self):
        logging.info("to_array")
        try:
            service = Service.create_service()
            logging.debug("service_create")
            history = (service.users().history().list(userId="me",
                                                      startHistoryId=self.start_history_id)
                       .execute())
            logging.debug("get_history")
            logging.debug(str(self.historyId) + ":" + str(history["historyId"]) + ":" + str(
                self.historyId == int(history["historyId"])) + str("nextPageToken" in history))
            history_list = history['history'] if 'history' in history else []

            if self.historyId == int(history["historyId"]):
                return history_list

            while 'nextPageToken' in history:
                print(history, history_list[0], history["historyId"])
                page_token = history['nextPageToken']
                history = (service.users().history().list(userId="me",
                                                          startHistoryId=self.start_history_id,
                                                          pageToken=page_token).execute())
                history_list = history['history'] if 'history' in history else []
                if self.historyId == int(history["historyId"]):
                    return history_list

        except errors.HttpError as error:
            logging.info('An error occurred: %s' % error)

history_idを解析すれば、messageのidを入手できます。idから本文や宛先などを取得するのは、マイナビニュースのページ[^3][^4]が詳しいです。

linebotを作る

developer trialならpush_messageが送れます。line-bot-sdk-pythonのREADMEを参考にしましょう

完成したもの

こんな感じ
スクリーンショット (39).png

[^1]: https://qiita.com/1ulce/items/672ae85d8c23bd9c478e
[^2]: https://stackoverflow.com/questions/49753494/access-to-google-search-console-api-through-google-app-engine-read-only-file-sy
[^3]: https://news.mynavi.jp/article/zeropython-22/
[^4]: https://news.mynavi.jp/article/zeropython-23/

9
5
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5