LINEに今日の天気情報を通知をしよう

Ruby

こんにちは、kazutoです。今回は、LINE NotifyというLINEが提供する公式アカウントを活用して、自分のLINE宛てに今日の天気情報を通知をしてみましょう。

事前準備

画像に alt 属性が指定されていません。ファイル名: 8bf6e9e2d87aac80a2fcfa0c7391ec58.png

まず初めに事前準備をしましょう。

  • ファイルを作成しよう
  • Wetaherクラスにattr_accessorメソッドを追加しよう
  • tokenを取得をしよう
  • confrig.rbファイルにtokenの情報を追加をしよう

ファイルを作成しよう

まずは、通知機能を実装するために新しくファイルを作成しましょう。

touch line_notify.rb
ls line_notify.rb
...
line_notify.rb

ファイルの作成ができたら次のステップに進みましょう。

Weatherクラスにattr_accessorメソッドを追加しよう。

次にWeatherクラスのインスタンスの属性である都市情報が格納されているインスタンス変数citysを外部から取得できる様にattr_accessorメソッドにセットしていきましょう。

class Weather
 #追加
  attr_accessor :citys
  def initialize(citys)
    @citys=citys
  end
 ...省略
end

attr_accessorメソッドを用いる事で、外部から値を取得、更新ができる様になります。今回は、LINEはTwitterと違って、1万文字までメッセージを送る事ができます

その特性を活かして、CSVファイルに格納されている都市の天気情報(今日)を全てを取得をして、LINEに通知するという仕様にして、Twitter api を用いて、作成した「天気予報 bot」と差別化をします。また、僕自身がiphoneを使っており、Siriに天気情報を訪ねるとOpenWeatherMap よりも詳細度が高く、数秒で、天気情報が手に入ります。なので、単一だけの都市の天気情報を取得をし、通知をしても、Siriという便利な機能があるので、正直、通知をする意味無いです。したがって、LINEで通知する際は、幅広い範囲の今日の天気情報を取得をして、メッセージを送る仕様がベストな選択です。 

※attr_accessorメソッドについて詳しく知りたい方は、下記の記事を参照してください。
関連記事:「Ruby~クラス3~

tokenを取得をしよう

次にLINE Notifiyにログインをして、tokenを取得をしていきましょう。下記のリンクからアクセスをしてください。

https://notify-bot.line.me/ja/

アクセスできたら、上記の画面になると思いますので、ログインをクリックをして、ログインを処理を行いましょう。(LINEアカウントのメールアドレスが必要です。)

次にマイページをクリックをしてください。

マイページ をクリックをして画面を遷移をしたら、スクロールをして「アクセストークンの発行(開発者向け)」あたりの「トークンを発行する」をクリックをしましょう。

トークン名(任意)を入力をして、発行するをクリックをしてください。その後、トークンが発行されますので、コピペをしてご自身のメモ帳などに貼り付けておいてください。

トークンをコピペをして、「連携中サービス」の欄に発行したトークン名があれば、LINE Notify Apiを使う準備が整いましたので、次のステップに進みましょう。

confrig.rbファイルにtokenの情報を追加をしよう

次に取得をしたトークンをconfrig.rbファイルにセットアップしていきましょう。まずは、先程、取得をしたtokenをvimを用いて、.zshrc ファイルの環境変数に格納をしていきましょう。

vim ~/.zshrc
...
export LINE_NOTIFY_TOKEN='(LINE_NOTIFYのTOKEN)を記述'
export LINE_NOTIFY_URL="https://notify-api.line.me/api/notify"

※忘れずに:wqを実行しましょう。
次にconfrig.rbファイルに環境変数を読み込んでいきましょう。

WEATHER_API_KEY       =  ENV['WEATHER_API_KEY']
WEATHER_URL           =  ENV['WEATHER_URL']

CONSUMER_KEY          =  ENV['CONSUMER_KEY']
CONSUMER_SECRET       =  ENV['CONSUMER_SECRET']
ACCESS_TOKEN          =  ENV['ACCESS_TOKEN']
ACCESS_TOKEN_SECRET   =  ENV['ACCESS_TOKEN_SECRET']

#ここから追加
LINE_NOTIFY_TOKEN     =  ENV['LINE_NOTIFY_TOKEN']
LINE_NOTIFY_URL       =  ENV['LINE_NOTIFY_URL']

では、環境変数の値を読み込めているか確認をしましょう。

puts LINE_NOTIFY_TOKEN
puts LINE_NOTIFY_URL

では、実行していきましょう。rubyコマンドを実行する前に、sourceコマンドを実行して、.zshrc ファイルの情報を更新をしてください。(vimをで書き換えた時点ではデータが反映されていないため)

source ~/.zshrc      
ruby config.rb 

値が確認ができたら次のトピックに進みましょう。

LineNotify(通知機能)クラスを実装

では通知機能を実装をしていきましょう。

下記の表がLINE Notify Apiをの仕様になります。

リクエストメソッド/ヘッダ 概要
Method POST
Content-Type application/x-www-form-urlencoded
OR
multipart/form-data
Authorization Bearer<access_token>

こちらの表は、LINE Notify Apiのリファレンスから持ってきました。詳しく仕様を確認をしたい方は、下記のリンクから参照してください。

https://notify-bot.line.me/doc/ja/

次に取得したトークンが正しく動くかの確認をしましょう。下記のコマンドを実行してください。

curl -X POST -H 'Authorization: Bearer <access_token>' -F 'message=test' \
https://notify-api.line.me/api/notify

上記の画像の通り、status200と表示をされたら、自分のLINE宛てにメッセージを送信できたはずです。ご自身のスマホで確認をしておきましょう。

  • initializeメソッドで属性を初期化をしよう。
  • create_requestメソッド
  • sendメソッド
require 'net/http'
require 'uri'
require 'json'
require 'open-uri'

class LineNotify

  def initialize(uri,token)
      @uri    = URI.parse(uri)
      @token  = token
  end

  def create_request(options)
    headers = { "Authorization"=>"Bearer #{@token}" , }
    request = Net::HTTP::Post.new(@uri,headers)
    request.set_form_data(options)
    request
  end

  def send(options)
    request = create_request(options)
    Net::HTTP.start(@uri.hostname, @uri.port, use_ssl: @uri.scheme == "https") do |req|
      res=req.request(request)
      puts res.body
    end
  end

end

それでは、メソッド事に解説をしていきます。

initializeメソッドで属性を初期化をしよう。

def initialize(uri,token)
      @uri    = URI.parse(uri)
      @token  = token
end

まずは、initializeメソッド用いて、インスタンスの属性を初期化をします。なお、初期化する属性は表の通りです。

属性 概要
@uri URIオブジェクト
@token LINE Notify APIのトークン

となります。まあ、LINE Notify APIを使うための準備を行います。

URI.parse(uri)

URIオブジェクトのparseメソッドを用いる事で、文字列のURIをURIに解析しています。

※URIとは
URIとは、Web上にあるあらゆるファイルを認識するための識別子の総称。
UAN、URLで構成される。

create_requestメソッド

def create_request(options)
    headers = {"Authorization"=>"Bearer #{@token}" ,}
    request = Net::HTTP::Post.new(@uri,headers)
    request.set_form_data(options)
    request
end

create_requestメソッドでは、リクエストを送る準備を行います。

 headers = { "Authorization"=>"Bearer #{@token}" }

まずは、HTTPヘッダの設定を行います。今回は、Authorization の設定を行って、LINE NotifyにBearer認証(アクセス権限の認可)をして、自分のスマホに通知をしていきます。Authorizationとは、アクセス権の設定などを参照して本人に与えられた適切な権限による操作を許可する事を指します。Bearer認証とは、期限付きトークンを利用した認証・認可に使用される認証方法です。

また、Bearer認証は、OAuthというSNSやWebサービス間でアクセス権限の認可」を行うためのプロトコルの中に含まれます。要するに複数のWebサービスを連携して動作させるためにOAuthというプロトコルを用いられ、その中の認証方法の一つとしてBearer認証をがあるという事です。

つまり、OAuthプロトコルの中のBearer認証を用いる事で、トークンを利用して、LINE Notify側(サーバ)に認証を受け、通知する権限の認可を受けて、初めて自分宛てに通知する事が可能になります。

 request = Net::HTTP::Post.new(@uri,headers)

こちらで、Net::HTTP::Postのインスタンスを生成をしています。引数には、先程、設定したヘッダ情報が格納された、変数headersと、URIが格納されている変数@uriを渡しております。

request.set_form_data(options)

次にset_form_dataメソッドの引数に変数options(パラメータ)を渡して、送るデータをセットしています。
ヘッダの Content-Type: には、 ‘application/x-www-form-urlencoded’ が設定されています。今回、LINE Notify api側がPOSTリクエストを送る際の指定で、’application/x-www-form-urlencoded’というMIMEタイプでリクエストを送る様に要求しています。

したがって、ヘッダ情報に’application/x-www-form-urlencoded’というMINEタイプを指定します。

※ Content-Typeとは
ファイルの種類を示す情報を指定する項目。MIMEタイプなどを指定する。

MINEタイプ
ファイルの種類を示す情報
charset
標準の文字エンコーディング
Content-Type: MINEタイプ,charset,boundary
例
Content-Type: application/x-www-form-urlencoded, charset=UTF-8

※MINEタイプとは
ファイルの種類を示す情報

タイプ名/サブタイプ名
例
text/html
text/xml
image/jpeg
image/png
application/json
application/x-www-form-urlencoded
 request

最終的にリクエスト情報が格納された変数requestをcreate_requestメソッドの返り値を返して処理が終了になります。

以上で、create_requestメソッドでの解説は終了になります。

sendメソッド

def send(options)
    request = create_request(options)
    Net::HTTP.start(@uri.hostname, @uri.port, use_ssl: @uri.scheme == "https") do |req|
      res=req.request(request)
      puts res.body
    end
  end

sendメソッドでは、実際にLINE NotifyにPOSTリクエストを送り、LINEに通知をします。

def create_request(options)
    headers = {"Authorization"=>"Bearer #{@token}" ,}
    request = Net::HTTP::Post.new(@uri,headers)
    request.set_form_data(options)
    request
end
request = create_request(options)

まずは、sendメソッド内で、create_requestメソッドを呼び出し、リクエスト情報を変数reuquestに格納をします。

Net::HTTP.start(@uri.hostname, @uri.port, use_ssl: @uri.scheme == "https") do |req|
      res=req.request(request)
      puts res.body
end

次にリクエスト情報をPOSTメソッド用いて、送信をしていきます。

Net::HTTP.start(@uri.hostname, @uri.port, use_ssl: @uri.scheme == "https") do |req|
end

まず、startメソッドを用いて、https通信を開始をします。引数には、

  • ホスト
  • ポート
  • スキーム

を渡して、どこのURLに対してどの方法でリクエストを送るか設定をしています。

res=req.request(request)

requestメソッドを用いて、POSTリクエストを送っています。今回は、インスタンスを、Net::HTTP::Postクラスから生成をしているため、requestメソッドでは、自動的にPOSTリクエストだと認識をしてくれます。

puts res.body
{"status":200,"message":"ok"}

レスポンスの中身を見てみましょう。statusというキーに200と表示されているのが確認できると思います。こちらは、HTTPステータスコードを表しており、200番はリクエストを成功した事を指します。したがって、あなたのLINEに通知が入っているはずです。

では、次のステップでLineNotifyクラスを用いて、実際に通知をを送ってみましょう。

通知をしてみよう

line_notify.rbファイルにメソッドを呼び出す記述を追加をして、ファイルを実行していきましょう。

require 'net/http'
require 'uri'
require 'json'
require 'open-uri'
#追加
require"/Users/kazuto/projects/environment/weather/config.rb"
class LineNotify

  def initialize(uri,token)
      @uri    = URI.parse(uri)
      @token  = token
  end

  def create_request(options)
    headers = { "Authorization"=>"Bearer #{@token}" , }
    request = Net::HTTP::Post.new(@uri,headers)
    request.set_form_data(options)
    request
  end

  def send(options)
    request = create_request(options)
    Net::HTTP.start(@uri.hostname, @uri.port, use_ssl: @uri.scheme == "https") do |req|
      res=req.request(request)
      puts res.body
    end
  end
end

#追加
line_notify=LineNotify.new(LINE_NOTIFY_URL,LINE_NOTIFY_TOKEN )
options = { message: "テスト"}
line_notify.send(options)
puts "成功"

では、rubyコマンドでファイルを実行をしていきましょう。

ruby line_notify.rb
...
{"status":200,"message":"ok"}
ok

LINEに通知が入っていたら、LineNotifyクラスの実装は完了です。

LINEに通知をしてみよう

次に実装したLineNotifyクラスを用いて、天気情報を取得をして、実際にLINEに通知をしていきましょう。

  • today_weather_in_tokyoメソッド
  • tomorrows_weatherメソッド
  • LINEに天気情報を通知をしてみよう
require"/Users/kazuto/projects/environment/weather/config.rb"
require"/Users/kazuto/projects/environment/weather/line_notify.rb"
require "json"
require "open-uri"
require "csv"
require "/Users/kazuto/projects/environment/weather/weather_class.rb"
require "/Users/kazuto/projects/environment/weather/tweet.rb"
require "/Users/kazuto/projects/environment/weather/search.rb"
require "/Users/kazuto/projects/environment/weather/analysis.rb"

#追加
def today_weathers()
  citys=CSV.read("/Users/kazuto/projects/environment/weather/citys.csv",headers: true)
  weather = Weather.new(citys)
  weathers=  weather.citys
  datas = weathers.map do |wt|
    datas = weather.get_weather_forecast(wt)
    current_time_weather=weather.current_time_weather(datas)
    "\n#{current_time_weather[:dt_txt]}\n#{current_time_weather[:city]}の天気\n天気:#{current_time_weather[:description]}\n気温:#{current_time_weather[:temperature]}\n#{"-"*30}"
  end
  datas.unshift("-"*30)
  text=datas.join()
  notify_todays_weather(text)
end

def notify_todays_weather(text)
  line_notify=LineNotify.new(LINE_NOTIFY_URL,LINE_NOTIFY_TOKEN )
  options = { message: text}
  line_notify.send(options)
  puts "成功"
end
today_weathers()

それでは、メソッド事に解説をしていきます。

today_weathersメソッド

def today_weathers()
  citys=CSV.read("/Users/kazuto/projects/environment/weather/citys.csv",headers: true)
  weather = Weather.new(citys)
  weathers=  weather.citys
  datas = weathers.map do |wt|
    datas = weather.get_weather_forecast(wt)
    current_time_weather=weather.current_time_weather(datas)
    "\n#{current_time_weather[:dt_txt]}\n#{current_time_weather[:city]}の天気\n天気:#{current_time_weather[:description]}\n気温:#{current_time_weather[:temperature]}\n#{"-"*30}"
  end
  datas.unshift("-"*30)
  text=datas.join()
  notify_todays_weather(text)
end

today_wetahersメソッドは、メソッド名の通り、今日(リアルタイム)の天気情報を取得するメソッドです。

 citys=CSV.read("/Users/kazuto/projects/environment/weather/citys.csv",headers: true)
 weather = Weather.new(citys)

まずは、CSVファイルを読み込み、変数citysに格納をします。その後、Wetaherクラスの引数に変数citysを渡して、インスタンスを生成をします。

weather=  weather.citys

次にCSVファイル内の都市情報を全て取得をして変数weathersに格納します。今回は、全ての天気情報を取得をしてLINEに通知をしたいためこの様な実装になりました。

datas =weathers.map do |wt|
    datas = weather.get_weather_forecast(wt)
    current_time_weather=weather.current_time_weather(datas)
    "\n#{current_time_weather[:dt_txt]}\n#{current_time_weather[:city]}の天気\n天気:#{current_time_weather[:description]}\n気温:#{current_time_weather[:temperature]}\n#{"-"*30}"
end

次に一つ一つの都市の天気情報を取得して、テンプレートを作成をしたいため、全ての都市情報が格納されている変数wetahersに対して、mapメソッド用いていきます。最終的に都市ごとの天気情報が入った、テンプレートを変数datasに格納をします。

mapのブロック内の処理を見ていきましょう。

datas = wetaher.get_weather_forecast(wt)

OpenWeatherMapにGETリクエストを送り、レスポンスとして、天気情報を取得をし、JSON形式にparseをしたデータを受け取っています。

current_time_weather=wetaher.current_time_weather(datas)

次に現在時間に一番近い(以降)、観測時間を抽出します。

  "\n#{current_time_weather[:dt_txt]}\n#{current_time_weather[:city]}の天気\n天気:#{current_time_weather[:description]}\n気温:#{current_time_weather[:temperature]}\n#{"-"*30}"

ブロックの最後に通知するテンプレートを作成して返り値として返します。

一度状況を整理をしてみましょう。

p  datas

["2020年11月27日 18時00分00秒
東京都の天気
天気:小雨
気温:13.19
------------------------------",

"2020年11月27日 18時00分00秒
大阪市の天気
天気:厚い雲
気温:12.53
------------------------------",

"2020年11月27日 18時00分00秒
沖縄市の天気
天気:小雨
気温:20.52
------------------------------",

"2020年11月27日 18時00分00秒
北海道の天気
天気:厚い雲
気温:-0.6
------------------------------"
]

この時点では、変数datasの中身は、上記の様に格納されています。正直、このままでも良いと思いますが、文頭に線を入れた方が、 都市ごとに区切られ、見やすいです。

datas.unshift("-"*30)
p  datas
[
"------------------------------",
"2020年11月27日 18時00分00秒
東京都の天気
天気:小雨
気温:13.19
------------------------------",

"2020年11月27日 18時00分00秒
大阪市の天気
天気:厚い雲
気温:12.53
------------------------------",

"2020年11月27日 18時00分00秒
沖縄市の天気
天気:小雨
気温:20.52
------------------------------",

"2020年11月27日 18時00分00秒
北海道の天気
天気:厚い雲
気温:-0.6
------------------------------"
]

したがって、unshiftメソッドを用いて、配列の先頭に点線を追加をします。

text=datas.join()

次にjoinメソッドを用いて、文字列を連結をさせ、通知するテキストを作成し、変数textに格納をします。

notify_todays_weather(text)

最後に通知するテキスト、変数textをnotify_todays_weatherメソッドに引数に渡して、呼び出します。

以上で、today_wetahersメソッドの解説を終了します。

notify_todays_weatherメソッド

def notify_todays_weather(text)
  line_notify=LineNotify.new(LINE_NOTIFY_URL,LINE_NOTIFY_TOKEN )
  options = { message: text}
  line_notify.send(options)
  puts "成功"
end

notify_todays_weatherメソッドは、今日の天気情報をLINEに通知するメソッドです。

line_notify=LineNotify.new(LINE_NOTIFY_URL,LINE_NOTIFY_TOKEN )

まずは、LineNotifyクラスのインスタンスを生成をします。引数には、リクエストを送るURL、LINE Notifyから取得したトークンを渡しています。

options = { message: text}

送るデータをハッシュ化します。

 line_notify.send(options)
 puts "成功"

最後にsendメソッドを呼び出して、LINEに通知を送ります。

以上で、notify_todays_weatherメソッドの実装を終了します。

LINEに天気情報を通知をしてみよう

最後にcronを用いて、定期実行ををしていきましょう。なお、詳しくcronについて解説はしませんので、気になる方はこちらの記事を参照してください。

関連記事:「天気予報 botを定期実行しよう」

def today_weathers()
  citys=CSV.read("/Users/kazuto/projects/environment/weather/citys.csv",headers: true)
  wetaher = Wetaher.new(citys)
  wetahers=  wetaher.citys
  datas = wetahers.map do |wt|
    datas = wetaher.get_weather_forecast(wt)
    current_time_weather=wetaher.current_time_weather(datas)
    "\n#{current_time_weather[:dt_txt]}\n#{current_time_weather[:city]}の天気\n天気:#{current_time_weather[:description]}\n気温:#{current_time_weather[:temperature]}\n#{"-"*30}"
  end
  datas.unshift("-"*30)
  text=datas.join()
  tomorrows_wetaher(text)
end

def tomorrows_weather(text)
  line_notify=LineNotify.new(LINE_NOTIFY_URL,LINE_NOTIFY_TOKEN )
  # text = "\n#{current_time_weather[:dt_txt]}\n#{current_time_weather[:city]}の天気\n天気:#{current_time_weather[:description]}\n気温:#{current_time_weather[:temperature]}"
  options = { message: text}
  line_notify.send(options)
end
#追加
today_weather()

periodic_execution.rbファイルの末尾にtoday_wetahersメソッドを呼び出す記述を付け足しください。次に、crontabファイルを開き、ジョブを設定していきます。

crontab -e
#文字コード(自分の環境に合わせる)
LC_CTYPE=ja_JP.utf8
LANG=ja_JP.utf8
#OpenWeatherMap API
WEATHER_API_KEY='(APIKEY)を入力'
WEATHER_URL='http://api.openweathermap.org/data/2.5/forecast'
#twitter API
CONSUMER_KEY ='(APIKEY)を入力します'
CONSUMER_SECRET='(APIKEYSECRET)を入力します'
ACCESS_TOKEN ='(ACCESSTOKEN)を入力します'
ACCESS_TOKEN_SECRET ='(ACCESSTOKENSECRET)を入力します'
#LINE Notify API
#追加
LINE_NOTIFY_TOKEN='(LINE_NOTIFYのTOKEN)を入力します'
LINE_NOTIFY_URL="https://notify-api.line.me/api/notify"
PATH=/Users/kazuto/opt/anaconda3/bin:/usr/local/opt/mysql@5.6/bin:/Users/kazuto/.rbenv/shims:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
#(分) (時) (日) (月) (曜)  実行したいコマンド
#追加
1 9 * * *  ruby -Ku /Users/kazuto/projects/environment/weather/periodic_execution.rb >> /Users/kazuto/projects/environment/weather/cron.log 2>&1
#LINE Notify API
#追加
LINE_NOTIFY_TOKEN='(LINE_NOTIFYのTOKEN)を入力します'
LINE_NOTIFY_URL="https://notify-api.line.me/api/notify"

confrig.rbファイルにtokenの情報を追加をしよう」で設定した、環境変数を.zshrc ファイルからコピペをして貼り付けましょう。

1 9 * * *  ruby -Ku /Users/kazuto/projects/environment/weather/periodic_execution.rb >> /Users/kazuto/projects/environment/weather/cron.log 2>&1

こちらの記述を付け足し、ジョブの設定が完了です。忘れずに:wpでファイルを保存をしましょう。

ここまで実装ができたら、明日の午前9時01分にあなたのLINEに上記の画像の通り、

  • 東京
  • 大阪
  • 沖縄
  • 北海道

の天気情報が通知がされるはずです。

まとめ:LINEに今日の天気情報を通知をしよう

今回は、LINE Notify APIを用いて、LINEに今日の天気情報を通知をしてみました。LINEは、1万文字までメッセージを送る事が可能なので、文字制限を気にせずに通知機能を実装する事ができました。

  • 東京
  • 大阪
  • 北海道
  • 沖縄

と4つの都市の天気情報を取得をしていますが、CSVファイルに都市情報を付け足して、取得する天気情報を増やすのもありだと思います。

以上、kazutoでした。