はじめに
2 歳の子がいるので、毎朝 E テレを観てるのですが、E テレでは天気が出ません
天気を確認するためだけにニュース番組にチャンネルを変えたり戻したりしてるので、天気 API から天気を取得して LINE に Push する簡単なアプリを作ろうと思います
年末の暇つぶしです
もちろん無料の範囲内で
構成

GitHub Actions を使ったことがなかったので、試しに使ってます
ただ、cron 機能しか使ってないので、これだけであれば GAS のトリガー機能とかの方が簡単で良いと思います
LINE の Developer アカウントの作成及び、アプリの登録
こちらから作成
https://developers.line.biz/ja/services/messaging-api
また、LINE Developer の管理画面に登録したアプリの QR コードがあるので、お友達登録しておく
天気 API のアカウント作成
OpenWeatherMap の API を使わせてもらいます
こちらからアカウント作成
https://home.openweathermap.org/users/sign_up
天気を取得して、LINE に Push するコード
import os
import json
import pandas as pd
from dotenv import load_dotenv
from urllib.request import urlopen
from datetime import datetime
from pytz import timezone
from linebot import LineBotApi
from linebot.models import TextSendMessage
def get_weather_icon(icon_str):
if icon_str == "01d" or icon_str == "01n":
return "☀️"
elif (
icon_str == "02d"
or icon_str == "02n"
or icon_str == "03d"
or icon_str == "03n"
or icon_str == "04d"
or icon_str == "04n"
):
return "☁️"
elif (
icon_str == "09d" or icon_str == "09n" or icon_str == "10d" or icon_str == "10n"
):
return "☂️"
elif icon_str == "11d" or icon_str == "11n":
return "⚡️"
elif icon_str == "13d" or icon_str == "13n":
return "☃️"
else:
return ""
def send_to_line(df):
texts = []
for k, v in df:
texts.append(f"【{k}】")
for _, d in v.iterrows():
texts.append(
f"{d['time']}時 {get_weather_icon(d['icon'])} {d['temp']}(℃) {d['rain']}(mm/3h)"
)
texts.append("")
line_bot = LineBotApi(os.getenv("LINE_ACCESS_TOKEN"))
line_bot.push_message(
os.getenv("LINE_USER_ID"), TextSendMessage(text="\n".join(texts))
)
def main():
url = "http://api.openweathermap.org/data/2.5/forecast"
id = os.getenv("OWM_PLACE_ID")
api_key = os.getenv("OWM_API_KEY")
res = urlopen(f"{url}?id={id}&appid={api_key}&lang=ja&units=metric").read()
res_json = json.loads(res)
arr_rj = []
for rj in res_json["list"]:
conv_rj = {}
timestamp = timezone("Asia/Tokyo").localize(datetime.fromtimestamp(rj["dt"]))
conv_rj["date"] = timestamp.strftime("%m/%d %a")
conv_rj["time"] = timestamp.strftime("%H")
conv_rj["description"] = rj["weather"][0]["description"]
conv_rj["icon"] = rj["weather"][0]["icon"]
conv_rj["temp"] = round(rj["main"]["temp"])
conv_rj["rain"] = round(rj["rain"]["3h"], 1) if "rain" in rj else 0
arr_rj.append(conv_rj)
send_to_line(pd.DataFrame(arr_rj).groupby("date"))
load_dotenv()
main()
上記のコードを実行すると、先程登録したアプリへ以下のように通知されます

メッセージを加工している処理が多いので、コードだけ見るとそこそこ長いのですが、天気情報取得と LINE に Push する処理だけにすると以下になります
python-dotenvを使って、トークンなどのセキュアな情報は環境変数に設定しています
def main():
url = "http://api.openweathermap.org/data/2.5/forecast"
id = os.getenv("OWM_PLACE_ID")
api_key = os.getenv("OWM_API_KEY")
res = urlopen(f"{url}?id={id}&appid={api_key}&lang=ja&units=metric").read()
...
今回は自分の住んでいる所だけの天気が分かれば良かったので、パラメータのid
に直接住んでいる地域の ID を渡しています
地域 ID はこちらからcity.list.json.gz
をダウンロードして確認できます
id
以外にも lat
(緯度)、lon
(経度)、zip
(郵便番号)をパラメータに指定して結果を取得することも可能です(今回作ったアプリは対応していません)
API_KEY はこちらから確認できます
def send_to_line(df):
...
line_bot = LineBotApi(os.getenv("LINE_ACCESS_TOKEN"))
line_bot.push_message(
os.getenv("LINE_USER_ID"), TextSendMessage(text="Hello")
LINE_ACCESS_TOKEN は LINE Developer の管理画面から発行できます
LINE_USER_ID も同管理画面から確認できます
複数人にメッセージを Push するやり方(2019/12/28 追記)
この記事を書いた時点では、作成した天気アプリをお友達登録した人全員にメッセージが通知されるって思い込んでたんですけど、上記のやり方だと自分にしかメッセージが通知されません
(すみません、その辺全然調べずに作ってました。。)
自分以外の人にも送る手順を書きます
自分以外の人に送るには送る人の UserID が分かれば良いのですが、知る方法が少し面倒です
UserID を知りたい人がアプリをお友達登録した時
に Line からこちらが用意したコールバック関数に Post リクエストで送ってもらって、その関数の中で確認するという方法を取ります
色々やり方はあると思いますが、以下に自分がやったやり方を書きます
コールバック関数を用意
GAS で作りました
var SLACK_ACCESS_TOKEN = ''
function doPost(e) {
SlackApp.create(SLACK_ACCESS_TOKEN).postMessage(
'#event-line',
JSON.stringify(e),
{ username: 'kurosame-gas' }
)
}
console.log(e)
でも確認できますが、その都度 GAS 開いて確認するのは面倒なので、Slack に通知しています
e
に UserID 等の情報が入っています
Webhook URL の取得
GAS の画面から公開 -> ウェブ アプリケーションとして導入
から Web API として公開できます
この時公開範囲は全員(匿名も含む)
にしてください
公開するとアプリの URL が画面上に出るので、控えておいてください
Webhook URL の設定
以下のように LINE Developer の管理画面で設定できます

ついでに、同画面で以下も Disabled に設定しておいてください

動作確認
作成した Line アプリのトーク画面で文字を送って、その文字と自分の UserID 等の情報が Slack に届いていれば成功です
また、他者がアプリをお友達登録したら、Slack に通知されるので、その人の UserID をゲットできます
Python から LINE に Push するコードを変更
# 変更前
line_bot.push_message(
os.getenv("LINE_USER_ID"), TextSendMessage(text="\n".join(texts))
)
# 変更後
line_bot.multicast(
os.getenv("LINE_USER_ID").split(","), TextSendMessage(text="\n".join(texts))
)
LINE_USER_ID にはカンマ区切りで UserID を設定しておきます
定期的に実行する
GitHub Actions の cron 機能を使って、毎朝 7 時に天気が通知されるように設定します
以下は Actions で読み込む yaml ファイルです
以下のファイルをルートディレクトリ上で.github/workflows
というディレクトリに入れておくだけで GitHub Actions が使えます
name: Weather
on:
schedule:
- cron: '00 22 * * *' # UTC
jobs:
weather:
name: Run weather
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Setup Python
uses: actions/setup-python@v1
with:
python-version: 3.7
- name: Install Pipenv
uses: dschep/install-pipenv-action@v1
- name: Install dependencies
run: pipenv sync
- name: Run weather
env:
OWM_PLACE_ID: ${{ secrets.OWM_PLACE_ID }}
OWM_API_KEY: ${{ secrets.OWM_API_KEY }}
LINE_ACCESS_TOKEN: ${{ secrets.LINE_ACCESS_TOKEN }}
LINE_USER_ID: ${{ secrets.LINE_USER_ID }}
run: pipenv run python3 ./bots/weather.py
Python コードの中で環境変数を使っているので、Actions のサーバー上で環境変数を使えるようにしてあげる必要があります
GitHub リポジトリのSettingsタブ -> Secrets
から Actions で使える環境変数を設定できます
そして上記のように secrets コンテキストを利用してランナーが環境変数を使えるようにしています
使ってみて少し気になったのが、Workflow が実行されるタイミングが cron で指定した時間より 5 分〜6 分くらい遅いですね
さいごに
LINE は妻も普段使いしているので、このアプリは使ってもらえそうです
(前に Slack 上で操作できるメモアプリを使った時は Slack を起動するのが手間になって使わなくなった)
今回実装したコードは以下にあげてます
https://github.com/kurosame/bots-python