Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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

動機

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

pub/subの設定

まずは、pub/subの設定をします。この設定をすることで、gmailに何らかの変更があった場合には、pub/subを通じてpostリクエストが届きます。
Gmail APIとPub/Subでリアルタイムメール受信 on ruby1の記事がとても参考になりました。この通りやったらうまくいきます
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から本文や宛先などを取得するのは、マイナビニュースのページ34が詳しいです。

linebotを作る

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

完成したもの

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

asamas
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした