この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
サーバーレス開発部の藤井元貴です。
以前、電車の運行情報を毎朝Slackに通知する仕組みを作りました。
このときは、毎朝8時に通知していましたが、
- 昼から外出するタイミングの運行情報は?
- 帰宅時の運行情報は?
- 休日の運行情報は?
なども知りたくなりました。人間とは欲深いものです。
そこで今回は、「今の運行情報」を自分だけに教えてくれるSlash Commandsを作成しました!!
おすすめの方
- 今の電車の遅延情報を知りたい
- AWS SAMで複数のLambda関数を定義したい
- Slackでアプリを作りたい
- SlackでSlach commandsを作りたい
- サーバーレスに興味がある
電車の運行情報
電車の運行情報については、下記のサイトを使用させていただきました。 使用にあたっては、下記サイトの「お約束」をご確認ください。
全体概要
前回の内容も含んでいます。
SlackのSlash comamnds
は、特定のURLに対して、「HTTP(POST)」でペイロードを送信します。
そのため、この情報を受け取るWebAPIを作成しています。
2つのLambda関数を作成しています。デプロイにAWS SAMを使用します。
Lambda関数 | 運行情報を見れる人 | やること |
---|---|---|
定期通知用 | チャンネルの全員 | 毎日、指定時刻に起動し、その時点の運行情報をSlackにPOSTする |
Slash commands用 | お願いした個人のみ | 好きなタイミングで個人が起動し、その時点の運行情報を個人向けに返答する |
- 定期通知用のLambda
- Slackの
incoming webhook
に対してメッセージをPOSTします - Slash commands用のLmabda
- Lambdaの戻り値(=API Gatewayの応答)でメッセージを返却します
- この処理は
3000ms以内
に実行する必要があります
環境
項目 | バージョン |
---|---|
macOS | High Sierra 10.13.6 |
AWS CLI | aws-cli/1.16.89 Python/3.6.1 Darwin/17.7.0 botocore/1.12.79 |
AWS SAM CLI | 0.10.0 |
Python | 3.6 |
AWS側の作成
Lambda関数
2つのLambda関数で共通利用するため、下記の処理を切り出しました。
- 通知対象の路線情報
- 共通の処理
Lambda関数は2つデプロイしますが、2つとも同じフォルダ(ファイル)をデプロイし、実行されるhandlerを変えています。 (他の方法としては、イイカンジにフォルダ構成を考える、Lambda Layerを使う、が考えられます。)
ファイル構成
主要なファイルの構成は下記となります。
├── hello_world
│ ├── common_lambda.py
│ ├── periodic.py
│ ├── requirements.txt
│ ├── slash_command.py
│ └── target.json
└── template.yaml
通知対象の路線ファイル(JSON)
この内容は任意にカスタマイズしてください!
target.json
[
{
"name": "中央・総武各駅停車",
"company": "JR東日本",
"website": "https://traininfo.jreast.co.jp/train_info/kanto.aspx"
},
{
"name": "東西線",
"company": "東京メトロ",
"website": "https://www.tokyometro.jp/unkou/history/touzai.html"
}
]
通知対象の路線を変更する場合は、このJSONファイルを修正すればOKです。
定期通知用
periodic.py
import os
import json
import requests
from common_lambda import get_notify_delays, get_message
SLACK_WEBHOOK_URL = os.environ['SLACK_WEBHOOK_URL']
def lambda_handler(event, context) -> None:
notify_delays = get_notify_delays()
if not notify_delays:
# 遅延が無ければ通知しない
return
# Slack用のメッセージを作成して投げる
(title, detail) = get_message(notify_delays)
post_slack(title, detail)
return
def post_slack(title, detail) -> None:
"""SlackにPostする
Args:
title: メッセージのタイトル
detail: メッセージの詳細(遅延情報)
Returns:
"""
# https://api.slack.com/incoming-webhooks
# https://api.slack.com/docs/message-formatting
# https://api.slack.com/docs/messages/builder
payload = {
'attachments': [
{
'color': '#36a64f',
'pretext': title,
'text': detail
}
]
}
# http://requests-docs-ja.readthedocs.io/en/latest/user/quickstart/
try:
response = requests.post(SLACK_WEBHOOK_URL, data=json.dumps(payload))
except requests.exceptions.RequestException as e:
print(e)
else:
print(response.status_code)
Slash command用
slash_command.py
import json
from urllib.parse import unquote
from common_lambda import get_notify_delays, get_message
def lambda_handler(event, context) -> dict:
# 受信したパラメータを解析する
request_param = parse_slash_commands(event['body'])
print(json.dumps(request_param))
if request_param['command'] != '/train':
# 想定コマンドと異なるため何もしない
return {
"statusCode": 200,
}
notify_delays = get_notify_delays()
# Slack用のメッセージを作成して返却する
(title, detail) = get_message(notify_delays)
# https://api.slack.com/incoming-webhooks
# https://api.slack.com/docs/message-formatting
# https://api.slack.com/docs/messages/builder
# https://api.slack.com/slash-commands
payload = {
'response_type': 'ephemeral', # コマンドを起動したユーザのみに返答する
'attachments': [
{
'color': '#36a64f',
'pretext': title,
'text': detail
}
]
}
return {
"statusCode": 200,
"body": json.dumps(payload)
}
def parse_slash_commands(payload) -> dict:
"""Slash commandsのパラメータを解析する
Args:
payload: 受信したSlash commandsのパラメータ
Returns:
dict: 解析したパラメータとその内容
"""
params = {}
key_value_list = unquote(payload).split("&")
for item in key_value_list:
(key, value) = item.split("=")
params[key] = value
return params
共通処理用
common_lambda.py
import json
import requests
JSON_ADDR = 'https://rti-giken.jp/fhc/api/train_tetsudo/delay.json'
def get_notify_delays() -> list:
"""通知すべき遅延情報を取得する
Returns:
list: 通知すべき遅延情報
"""
current_delays = get_current_delays()
target_list = get_target_list()
notify_delays = []
for delay_item in current_delays:
for check_item in target_list:
if delay_item['name'] == check_item['name'] and delay_item['company'] == check_item['company']:
notify_delays.append(check_item)
return notify_delays
def get_target_list() -> list:
"""判定対象の路線情報を取得する
Returns:
list: 判定対象の路線情報
"""
with open('target.json') as f:
return json.load(f)
def get_current_delays() -> list:
"""
現在の遅延情報を外部サイトから取得する
Returns:
list: 現在の遅延情報
"""
try:
res = requests.get(JSON_ADDR)
except requests.RequestException as e:
print(e)
raise e
if res.status_code == 200:
return json.loads(res.text)
return []
def get_message(delays) -> tuple:
"""Slackに通知するメッセージを作成する
Args:
delays: 通知すべき遅延情報
Returns:
str: メッセージのタイトル
str: メッセージの詳細(遅延情報)
"""
if not delays:
return "電車の遅延はありません。", ""
title = "電車の遅延があります。"
details = []
for item in delays:
company = item['company']
name = item['name']
website = item['website']
details.append(f'・{company}: {name}: <{website}|こちら>')
return title, '\n'.join(details)
デプロイまで実行する
S3バケットの作成
コード等を格納するためのS3バケットを作成します。作成済みの場合は飛ばします。
aws s3 mb s3://cm-fujii.genki-deploy
ビルド
下記コマンドでビルドします。
sam build
package
続いてコード一式をS3バケットにアップロードします。
sam package \
--output-template-file packaged.yaml \
--s3-bucket cm-fujii.genki-deploy
deploy
最後にデプロイします。template.yaml
の環境変数をオーバーライドし、ここでSlackのWebhook URL
を設定します。
(詳細は前回を参照してください)
sam deploy \
--template-file packaged.yaml \
--stack-name NotifyTrainDelayToSlack \
--capabilities CAPABILITY_IAM \
--parameter-overrides SlackWebhookUrl=https://hooks.slack.com/services/xxxxxxxxxxxxx
WebAPIの確認
WebAPIのURLを確認します。
$ aws cloudformation describe-stacks --stack-name NotifyTrainDelayToSlack --query 'Stacks[].Outputs'
[
[
{
"OutputKey": "SlashCommandApi",
"OutputValue": "https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/train/notification",
"Description": "Slash command API"
}
]
]
このURL(OutputValue
)は、SlackのSlash commandsの設定で使用します。
(Slash commandsがこのURLを叩くようにする)
Slackの準備
アプリの作成
Slackのアプリを作成します。
作成画面
任意のチャンネルを開き、「アプリを追加する」を選択します。
ブラウザが開くので、右上の「ビルド」を選択します。
左側にある「Building Slack apps」を選択します。
少し下側にある「Create a Slack app」を選択します。
もしくは、下記にアクセスすればOKです。
- <https://api.slack.com/apps>
新規作成
表示された画面の「Create New App」を選択します。
「App Name」を入力し、「Development Slack Workspace」を選択し、「Create App」を選択します。
これでアプリの作成まで完了しました。
アプリの設定
Slash Commandsの設定を行い、アプリをワークスペースにインストールします。
Slash Commands
「Slash Commands」を選択し、設定を行います。
「Create New Command」を選択します。
Commandに「/train」と入力し、Request URLに「さきほど確認したAPI GatewayのURL」を入力し、Short Descriptionに「簡単な説明」を入力します。
入力後は「Save」を選択します。
Basic Information
「Basic Information」を選択し、作成したアプリをワークスペースにインストールします。
「Install your app to your workspace」の「Install App to Workspace」を選択します。
「許可する」を選択します。
これで完了です!
使ってみる
Slackで/train
と入力します。
遅延があるとき
遅延がないとき
さいごに
EC2などのサーバーを立てなくても、簡単に実行できる環境が作れました。
サーバーレスは楽しいですね!!