この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
天気予報を毎日見ていますか? 私は見ていません。そのせいで「夕方になったら雨が降ってきた……。スーパーは昼に行けばよかった……。」といった後悔は1度や2度ではありません。そこで考えました。天気予報をSlackに通知すれば否が応でも見るだろう、と。
通知内容と頻度
- 通知内容
- 今日の天気:1時間ごと(12時間分)
- 週間の天気:1日ごと(8日分)
- 通知頻度
- 毎日0時から1時間毎
実際の様子がこちらです。
システム概要
超シンプルなサーバーレス構成です。
通知先の準備
Slackチャンネルを作成
notify-weather-tokyo
チャンネルを作成しました。
Slackアプリを追加
https://api.slack.com/apps にアクセスし、Slackアプリを作成します。
Incoming Webhooksを有効化
Incoming Webhooks
設定をONにします。
Incoming Webhookの作成 & 通知先チャンネル設定
先ほど作成したチャンネルを通知先に設定します。
設定完了です。URLはあとでパラメータストアに登録します。
OpenWeatherのAPIキーを取得
OpenWeatherMapにサインアップし、APIキーを取得します。
サーバーレスアプリの作成
パラメータストアに値を追加
AWS Systems Managerのパラメータストアに次の値を追加します。
- 緯度
- 経度
- Slackの通知先URL
- OpenWeatherMapのAPIキー
緯度経度は東京を設定しています。
緯度
aws ssm put-parameter \
--type 'String' \
--name '/Notify-Weather-To-Slack-App/tokyo/Latitude' \
--value '35.681236'
経度
aws ssm put-parameter \
--type 'String' \
--name '/Notify-Weather-To-Slack-App/tokyo/Longitude' \
--value '139.767125'
Slackの通知先URL
URLの先頭にhttps://
があると、コマンド実行が失敗するため取り除いています。
aws ssm put-parameter \
--type 'String' \
--name '/Notify-Weather-To-Slack-App/tokyo/slack_url' \
--value 'hooks.slack.com/services/xxxxx/yyyyy/zzzzz'
OpenWeatherのAPIキー
aws ssm put-parameter \
--type 'String' \
--name '/Notify-Weather-To-Slack-App/apikey' \
--value 'xxxx'
AWS SAMプロジェクトの作成
sam init --runtime python3.7 --name Notify-Weather-To-Slack-App
テンプレートファイル
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Notify-Weather-To-Slack-App
Parameters:
TargetName:
Type: String
Latitude:
Type: AWS::SSM::Parameter::Value<String>
Longitude:
Type: AWS::SSM::Parameter::Value<String>
ApiKey:
Type: AWS::SSM::Parameter::Value<String>
SlackUrl:
Type: AWS::SSM::Parameter::Value<String>
Resources:
NotifyWeatherFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub notify-weather-to-slack-${TargetName}-function
CodeUri: src/
Handler: app.lambda_handler
Runtime: python3.7
Timeout: 10
Environment:
Variables:
LATITUDE: !Ref Latitude
LONGITUDE: !Ref Longitude
API_KEY: !Ref ApiKey
SLACK_URL: !Ref SlackUrl
Events:
NotifySlack:
Type: Schedule
Properties:
Schedule: cron(0 0/1 * * ? *) # 日本時間で毎日0時から1時間毎
NotifyWeatherFunctionLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/lambda/${NotifyWeatherFunction}"
Lambdaコード
app.py
import json
import locale
import os
import requests
from datetime import datetime, timezone, timedelta
LATITUDE = os.environ['LATITUDE']
LONGITUDE = os.environ['LONGITUDE']
API_KEY = os.environ['API_KEY']
SLACk_URL = os.environ['SLACK_URL']
def lambda_handler(event, context):
main()
def main():
# 曜日表記を日本語にする
locale.setlocale(locale.LC_TIME, 'ja_JP.UTF-8')
# 気象情報を取得する
weather_endpoint = get_weather_endpoint(LATITUDE, LONGITUDE, API_KEY)
weather_data = get_weather_data(weather_endpoint)
# Slack通知用のメッセージを作成する
message = create_message(weather_data)
print(json.dumps(message))
# Slackに通知する
post_slack(message)
def get_weather_endpoint(latitude, longitude, api_key):
base_url = 'https://api.openweathermap.org/data/2.5/onecall'
return f'{base_url}?lat={latitude}&lon={longitude}&exclude=current&units=metric&lang=ja&appid={api_key}'
def get_weather_data(endpoint):
res = requests.get(endpoint)
return res.json()
def convert_unixtime_to_jst_datetime(unixtime):
return datetime.fromtimestamp(unixtime, timezone(timedelta(hours=9)))
def get_icon_url(icon_name):
return f'http://openweathermap.org/img/wn/{icon_name}@2x.png'
def create_message(weather_data):
hourly = create_message_blocks_hourly(weather_data)
daily = create_message_blocks_daily(weather_data)
message_blocks = []
message_blocks += hourly
message_blocks.append({
'type': 'divider'
})
message_blocks += daily
return {
'blocks': message_blocks
}
def create_message_blocks_hourly(weather_data):
message_blocks = []
hourly = weather_data['hourly']
# 見出しを作る
first_datetime = convert_unixtime_to_jst_datetime(hourly[0]['dt'])
message_blocks.append({
'type': 'section',
'text': {
'type': 'plain_text',
'text': first_datetime.strftime('%m/%d(%a)')
}
})
# 1時間毎のメッセージを作る(12時間分)
for i in range(12):
item = hourly[i]
target_datetime = convert_unixtime_to_jst_datetime(item['dt'])
description = item['weather'][0]['description']
icon_url = get_icon_url(item['weather'][0]['icon'])
rain = item.get('rain', {'1h': 0}).get('1h') # rainが無い場合は0mm/hとする
temperature = item['temp']
humidity = item['humidity']
pressure = item['pressure']
wind_speed = item['wind_speed']
message_blocks.append({
'type': 'context',
'elements': [
{
'type': 'mrkdwn',
'text': target_datetime.strftime('%H:%M')
},
{
'type': 'image',
'image_url': icon_url,
'alt_text': description
},
{
'type': 'mrkdwn',
'text': f'{description: <6} {rain:>5.1f}mm/h {temperature:>4.1f}℃ '
f'{humidity}% {pressure:>4}hPa {wind_speed:>5.1f}m/s'
}
]
})
return message_blocks
def create_message_blocks_daily(weather_data):
message_blocks = []
daily = weather_data['daily']
# 見出しを作る
message_blocks.append({
'type': 'section',
'text': {
'type': 'plain_text',
'text': '週間天気'
}
})
# 1日毎のメッセージを作る
for item in daily:
target_datetime = convert_unixtime_to_jst_datetime(item['dt'])
description = item['weather'][0]['description']
icon_url = get_icon_url(item['weather'][0]['icon'])
rain = item.get('rain', 0) # rainが無い場合は0mm/hとする
temperature_min = item['temp']['min']
temperature_max = item['temp']['max']
humidity = item['humidity']
pressure = item['pressure']
wind_speed = item['wind_speed']
message_blocks.append({
'type': 'context',
'elements': [
{
'type': 'mrkdwn',
'text': target_datetime.strftime('%m/%d(%a)')
},
{
'type': 'image',
'image_url': icon_url,
'alt_text': description
},
{
'type': 'mrkdwn',
'text': f'{description: <6} {rain:>5.1f}mm/h {temperature_min:>4.1f} - {temperature_max:>4.1f}℃ '
f'{humidity}% {pressure:>4}hPa {wind_speed:>5.1f}m/s'
}
]
})
return message_blocks
def post_slack(payload):
url = f'https://{SLACk_URL}'
try:
response = requests.post(url, data=json.dumps(payload))
except requests.exceptions.RequestException as e:
print(e)
else:
print(response.status_code)
デプロイ
parameter-overrides
オプションでパラメータストアの情報を渡しています。
sam build
sam package \
--output-template-file packaged.yaml \
--s3-bucket your-bucket-name
sam deploy \
--template-file packaged.yaml \
--stack-name Notify-Weather-To-Slack-App-tokyo \
--capabilities CAPABILITY_NAMED_IAM \
--no-fail-on-empty-changeset \
--parameter-overrides \
TargetName=tokyo \
Latitude=/Notify-Weather-To-Slack-App/tokyo/Latitude \
Longitude=/Notify-Weather-To-Slack-App/tokyo/Longitude \
ApiKey=/Notify-Weather-To-Slack-App/apikey \
SlackUrl=/Notify-Weather-To-Slack-App/tokyo/slack_url
動作確認
しばらくすると……、無事に通知がきました!!
さいごに
天気予報をSlackに通知してみました。これで天気予報を見る習慣ができるでしょう。