この記事はCAMPHOR- advent calender 2018の23日目の記事です
動機
私の所属している東洋医学研究会(通称:東医研)には、専用のgmailアカウントがありますが、ほとんどの人がメールを見ていないので、今までは受信や送信メールがあれば、人力でグループLINEに投稿していました。これをbotでなんとかしたかった。
pub/subの設定
まずは、pub/subの設定をします。この設定をすることで、gmailに何らかの変更があった場合には、pub/subを通じてpostリクエストが届きます。
Gmail APIとPub/Subでリアルタイムメール受信 on ruby1の記事がとても参考になりました。この通りやったらうまくいきます
- トピックを設定する
- サブスクリプションを設定する
私の場合は、サブスクリプションの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のページを参考にします
{
"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
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を保存するようにしました
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を参考にしましょう