アプリ名
説明
ただいまの言葉をきっかけに親からの伝言を子供へ伝えます。
忙しくて電話もできない、手紙も書く時間がない親に変わってテキスト形式で子供への伝言を預かります。
子供の周りに起きている事件の情報、洗濯物の天敵の雨の情報、よく遊ぶ子供が避けたいインフルエンザの情報を親に届けます。
紹介動画
エントリーページ
機能概要
- 伝言板機能
- ドア君アカウントに対してメッセージを登録
- clovaでスキルを起動 -> 伝言板を読み上げ・メッセージプッシュが行われる
- line_user_idにより紐付けを行った。
- お天気プッシュ機能
- OpenWeatherApiよりお天気情報を取得し、3時間後に雨が降る場合にプッシュ通知
- https://openweathermap.org/
- 不審者プッシュ機能
- ドア君アカウントに対してメッセージで不審者情報のプッシュ通知をする地区を登録
- 警視庁ホームページより、情報を取得し、プッシュ通知
- インフルエンザプッシュ機能
- GoogleTrendsより、「インフルエンザ」の検索結果により流行を予測
使用技術
- 使用言語: Ruby
- フレームワーク: RubyOnRails
- デプロイ: heroku
- linebot
- clova
Messaging Api
rubyなので、line-bot-api
gemを利用
gem 'line-bot-api'
プッシュ通知
require 'line/bot'
require 'dotenv'
class LineBotClient
# OPTIMIZE このクラスにLineBotへのリクエストをまとめる
# to: userId、groupId、またはroomId
# https://developers.line.me/ja/reference/messaging-api/#anchor-0c00cb0f42b970892f7c3382f92620dca5a110fc
def pushMessage(type='text', text="", to)
message = {
type: type,
text: text
}
client.push_message(to, message)
end
private
def client
@@client ||= Line::Bot::Client.new { |config|
config.channel_secret = ENV["LINE_CHANNEL_SECRET"]
config.channel_token = ENV["LINE_CHANNEL_TOKEN"]
}
end
end
第一引数: プッシュメッセージのタイプ
第二引数: 送信するテキスト情報
第三引数: userId、groupId、またはroomId
line_bot_client = LineBotClient.new
line_bot_client.pushMessage('text', text, to)
clovaスキル
rubyに対応しているライブラリはないので、直接CEK_APIを叩く
https://clova-developers.line.me/guide/#/CEK/References/CEK_API.md
clovaの応答
@shouldEndSession
の値がfalseだとクローバのスキルが終了せずに再度待ち状態となる。
だが、@shouldEndSession
の値がfalseでもvoice_message
が存在しない場合は、セッションが終了する仕様であった。
class ClovaController < ApplicationController
require 'line/bot'
before_action :set_clova, only: [:callback]
before_action :set_shouldEndSession, only: [:callback]
def callback
@voice_message = "おかえりなさい!"
if @shouldEndSession
# ラインボットへ通知を送る
send_push_message
# @voice_messageの設定
set_voice_message
end
render 'clova/callback', formats: 'json', handlers: 'jbuilder'
end
private
def set_shouldEndSession
# スキルの起動時のみfalseとする
slots = params['request']['intent']['slots']
@shouldEndSession = slots.nil? ? false : true
end
def send_push_message
...
end
def set_voice_message
...
end
end
callback.json.jbuilder
json.version "1.0"
json.response do |response|
response.outputSpeech do |outputSpeech|
outputSpeech.type "SimpleSpeech"
outputSpeech.values do |values|
values.type "PlainText"
values.lang "ja"
values.value @voice_message
end
end
response.card {}
response.directives []
response.shouldEndSession @shouldEndSession
end
お天気情報取得
OpenWeatherMapApiを利用。無料プランで、3時間毎のお天気情報が取得できる。
サンプル
require "json"
require "open-uri"
class OpenWeatherMap
BASE_URL = "https://api.openweathermap.org/data/2.5/forecast".freeze
TOKYO_CITY_ID = '1850147'
# tokyo: key='id', val='1850147'
# akashi: key='q', val='Akashi'
def self.get(key = 'id', val = TOKYO_CITY_ID)
url = BASE_URL + "?#{key}=#{val}&APPID=#{ENV['Open_Weather_Map_API_KEY']}"
puts "Request method:GET, URL:#{url}"
json_response = open(url)
JSON.parse(json_response.read)
end
end
tokyo_city_id = '1850147'
tokyo_weather_infos = OpenWeatherMap.get('id', tokyo_city_id)
不審者情報
Mechanizeでのスクレイプ。
1日毎に情報が更新されるため、1日に一回クーロンで実行。
namespace :patrol do
desc "東京都の不審者情報取得"
task :tokyo_police_page => :environment do
SLEEP_TIME = 1.second.freeze
puts "Start scrape..."
agent = Mechanize.new
top_page = agent.get("http://www.keishicho.metro.tokyo.jp/kurashi/higai/kodomo/fushin/index.html")
sleep(SLEEP_TIME)
detail_urls = top_page.search('.norcor').search('a').map{|e| e.get_attribute(:href) }
detail_urls.each_with_index do |detail_url, i|
sleep(SLEEP_TIME)
# only scrape newest day
break if i > 0
# move to detail page
detail_page = top_page.link_with(href: detail_url).click
city_infos = detail_page.search('tr')
city_infos.each do |city_info|
# input suspicious person infomation
city_name = city_info.children.search('th > p').children.first.text
suspicious_person_info_text = city_info.children.search('td > p').first.children.map{ |a| a.text }.reject(&:empty?).join('\n')
tx = city_info.children.search('ul > li').first&.children&.map{ |a| a.text }&.reject(&:empty?)&.join('\n')
suspicious_person_info_text += "\n" + tx if tx
if city = City.find_by(name: city_name)
puts "find #{city_name}"
city.suspicious_person_infos.find_or_create_by(
text: suspicious_person_info_text,
source_url: detail_url,
published_at: Time.now.beginning_of_day)
else
puts "Error: Not found city name #{city_name}"
end
end
end
puts "Finish scrape.\n"
puts "Start send message..."
send_alart_push_message()
puts "Finish send message."
end
def send_alart_push_message()
SuspiciousPersonInfo.tell_infos.each do |line_user_id, cities|
text = create_message_text(cities).chomp
response = send_message_text(text, line_user_id.to_s)
puts "Sent message. line_user_id:#{line_user_id} response:#{response}"
end
end
def send_message_text(text, to)
@line_bot_client ||= LineBotClient.new
@line_bot_client.pushMessage('text', text, to)
end
def create_message_text(cities)
# TODO 不審者情報が見やすいようなページを作成
# tokyo_police_page = "http://www.keishicho.metro.tokyo.jp/kurashi/higai/kodomo/fushin/index.html"
<<-EOS
<不審者情報>
# {cities.map(&:name).join(', ')} にて不審者が確認されました。
EOS
end
end
インフルエンザ情報
取得したCSVをManagementページで追加すると、CSVからインフルエンザのトレンド情報を取得する。そのトレンドからインフルエンザを予測。
現状は、自動でCSVを取得する部分が未完成。
require 'csv'
class GoogleTrendsManager
def initialize(a = nil, b = nil)
@csv_file = nil
if a.class == ActionDispatch::Http::UploadedFile && b == nil
import_csv_by_file(a)
elsif a.class == String && b.class == String
import_csv_by_crowl(a, b)
else
raise "Unexpected params."
end
end
def get_trends
return unless @csv_file
trends = {}
CSV.foreach(@csv_file.path, headers: false).with_index do |row, i|
next if i < 3
time, score = row
trends[time] = score
end
trends
end
private
def import_csv_by_file(file)
@csv_file = file
end
def import_csv_by_crowl(q='', geo='JP')
# TODO Seleniumでのクローリング -> CSVファイル取得
...
end
end
参考
https://developers.line.me/ja/docs/
https://openweathermap.org/
http://www.keishicho.metro.tokyo.jp/
https://qiita.com/4geru/items/7fdd418ce8c1059001c6