見出し画像

Python(Flask)でEDINETの有価証券報告書をSlack経由で取得してみた(with AWS lambda)

前回の続き
というわけでタイトル通り、前回作成した「EDINET APIから有価証券報告書等をかっさらってくるpythonスクリプト」を改良し、更にSlack via Flaskでファイルを取得できるようにしたものをAWS lambdaにぶち込みました。

下記SlackとやりとりするFlaskAPPのコード
(※書類取得スクリプトはAPIに大量にリクエストを送りかねない仕様であり、noteに公開し不特定多数の方が私のスクリプトを真似てなんやかんやする恐れがあり、最悪の場合私が偽計業務妨害で豚箱にぶちこまれてしまう可能性があるかもしれないので今回は投稿しません。ご了承ください。人生に疲れたら投稿します。)

import json
import requests
from slackclient import SlackClient
from flask import Flask, request, make_response

#例のスクリプト↓
import edinet

#slackでオシャレなメニューを表示するためのjsonデータ↓
#別にpythonファイルに記述しなくても別の形式でデータを呼び出しても良い。というかそうしたほうが良い。
import attachments

verificatin_token = "YOUR VERIFICATION TOKEN"
bot_token = "YOUR BOT TOKEN"

slack = SlackClient(token=bot_token)

app = Flask(__name__)

#Webhookテスト用エンドポイント
@app.route("/")
def test():
 return("Hello!!")

#slackから自作コマンドを呼びだされた時のエンドポイント
@app.route("/button", methods=["POST"])
def command_selection():
 
 #ペイロード全体の取得。jsonではないので注意。
 payloads = request.form
 
 #自作ボットとのダイレクトメッセージのチャネルを取得
 channel = payloads["channel_id"]

 #冒頭で説明したオシャレねメニューを表示するためのjsonデータ代入
 #大した内容ではないので、気になる方はslackのattachmentsページでもっとオシャレなの見てきてください。
 attachments_json = attachments.attachments

 #トークン確認
 if verificatin_token != payloads["token"]:
   #403を素直に返すと処理が止まっちゃうので {"X-Slack-No-Retry": 1} でごまかす
  return make_response(("Invalid token", 403, {"X-Slack-No-Retry": 1}))
 
 #オシャレなメニューとWhat do you wanna see ?をユーザに送る
 reply(channel, "What do you wanna see ?", attachments_json)
 #とりあえずレスポンスコード200を返してFlaskをごまかす
 return make_response("", 200)

#What do you wanna see ?に返事がきたときのエンドポイント
@app.route("/button/result", methods=["POST"])
def reports_handler():
 json_form = json.loads(request.form["payload"])
 
 #ユーザーの返事内容を取得
 value = json_form["actions"][0]["value"]
 channel = json_form["channel"]["id"]
 
 #自作pythonスクリプトから報告書やろをとってきてもらう
 files = edinet.get_contents(value)

 #何も取得できなかったら警告
 if files == []:
   reply(channel, "Something went wrong. You might be selecting off day.")
   return make_response("", 200)
 else:
   try:
   #取得したファイルをユーザに送信
     send_reports(channel, files, value)
     return make_response("", 200)
   except Exception as e:
     #タイムアウト例外処理
     reply(channel, "Something went wrong. The error " + str(e))
     return make_response("", 403, {"X-Slack-No-Retry": 1})

#slackからテキストメッセージがきたときのエンドポイント
@app.route("/slack", methods=["POST"])
def event_handler():
 slack_events = json.loads(request.data)
 
 #webhook用にエンドポイントを登録する時のverification処理
 if "challenge" in slack_events:
   return make_response(slack_events["challenge"], 200, {"content-type": "application/json"})

 #ヘッダー取得
 headers = request.headers
 
 #Slackはエンドポイントから3秒間応答がないと、数回リクエストを送ってくる
 #後述の、ボットからのメッセージは無視し200レスポンスを返すを実行しても
 #何故かSlackがリクエストを送ってくるので {"X-Slack-No-Retry": 1} でやりすごす
 if "X-Slack-Retry-Reason" in headers and (headers["X-Slack-Retry-Reason"] == "http_error" or headers["X-Slack-Retry-Reason"] == "http_timeout"):
   return make_response("Ignoring retry", 200, {"X-Slack-No-Retry": 1})
  
 if verificatin_token != slack_events["token"]:
   return make_response(("Invalid token", 403, {"X-Slack-No-Retry": 1}))
 
 #何かしらのイベント(今回ならメッセージイベント)を検知したらmessage_handlerを呼び出す
 if "event" in slack_events:
   event_type = slack_events["event"]["type"]
   if event_type == "message":
     return(message_handler(slack_events))
 else:
     return("", 403, {"X-Slack-No-Retry": 1})

#メッセージハンドル関数
def message_handler(slack_events):
 user = slack_events["event"]["user"]  
 channel = slack_events["event"]["channel"] 

 #メッセージを送ってきているユーザが誰か確認
 if user != "USER ID" :
   #ボットによるメッセージならとりあえず200だけ返して無視
   return make_response("", 200)
 else:
   reply(channel, 'Execute command "selection"')
   return make_response("", 200) 

#ファイル送信関数
def send_reports(channel, files, value):
 return(slack.api_call(
   #SlackAPIのファイル送信メソッド
   "files.upload",
   channels=channel,
   file=files,
   initial_comment=value,
  timeout=120,
   ))

#メッセージ送信関数
def reply(channel, response, attachments=None):
 return(slack.api_call(
   #SlackAPIのメッセージ送信メソッド
  "chat.postMessage",
   as_user=True,
   channel=channel,
   text=response,
   attachments=attachments,
   ))

if __name__ == "__main__":
 app.run() 

以下、実行結果

自作コマンド/selectionを叩くと”有価証券報告書” ”半期” "有価証券届出書"
の3つの選択メニューが返され、どれかを選ぶと、その選択した文字列を含む、本日提出された報告書を取得してzipで返してくれる。(所要時間約1〜3分)
メニュー選択後のタイムアウトエラーは、サーバが3秒以内にSlackにレスポンスを返していないがために表示される。機能としては問題なく動いているが、イケてないのでマルチスレッドなりで、書類取得処理をバックグラウンドで実行し、同時にSlack側にレスポンスを返す作りにしたほうがよい。でも疲れたのでしばらく改良はしない。

そして、ある種私の技術ドメインでもあるAWS lambdaに関しては、折角つくったFlaskアプリをlambda用に書き直すのが面倒くさかったので、python lambdaをwsgi仕様にいい感じしてくれるZappaをつかってプログレッシブにデプロイしました。

おしまい。

(PEP8とか読んだことないのでコードの書き方や諸々アドバイスあれば是非お願いします)

この記事が気に入ったらサポートをしてみませんか?