Ruby SDK, Push Message API, Scheduler
LineBot+Herokuの記事を見ると未だに頑張ってFixieでIPを固定しようとしていたり,SDKを使っていなかったり,オウム返しばかりでPush Message APIについての情報がなかったりで結構情報がアップデートされていない感じがあったため,LineBotの2016年12月版として書いて見ようと思います.何を作ったのか?
現在シェアハウスに住んでいるのですが,ゴミ出しとか特にルールが無いと大体特定の人に偏ります.というわけでゴミ出し当番を決めてかつゴミの日をリマインドするBotを作ることにしました.住民間のコミュニケーションは主にLineなので,必然的にLineBotを作ることに.サーバーはAWS使ってとかやり始めるとしんどそうだったのため,Heroku(+Scheduler)で行くことにしました.
Githubにソースコードは公開しています.
https://github.com/shuheiktgw/tombot
アカウント発行などの下準備
この辺を参考にしながら作業しました.http://qiita.com/YoheiMiyamoto/items/f6851cdb40891edf0e57
http://qiita.com/pokochi/items/cc06e3f6edc9136446ca
上記に書いてない注意点としては以下のような感じです.
LineのアカウントタイプはDevelopper Trialを選択する
意外なハマりポイントとしてはLineのMessage APIのアカウントタイプに2種類ありまして,今回はリマインドメッセージをBot側から送りつける必要があるため必ずDevelopper Trialの方を選択してください(Developper TrialじゃないとPush Message API使えないらしい).FixieとかLine側のIPのWhitelistの設定は不要
大体Heroku*LineBot系の記事を見るとFixieというHeorkuのAddOnを使ってIPを固定しようとしているのですが,Line側のIPのWhitelistの設定は今年の4月ぐらいから任意になっているためその辺の設定不要です(FixieがあるとアウトバウンドのProxyを指定しなくてはいけないのでSDKが使えなくなる).参考: https://developers.line.me/news?ny=2016&nm=04
rails newは--database=postgresqlをつけて
Heorkuがpostgresqlで動いているため,newするときにこれをつけておくと楽です.その他のHeroku周りのやつは以下を参照してみてください.参考: Getting Started with Rails 5.x on Heroku
参考: Getting Started with Rails 4.x on Heroku
LineのRuby SDKを使った場合の設定
大体ここに書いてあります. https://github.com/line/line-bot-sdk-rubyRailsだとGemfileに以下の一行を足します.
gem 'line-bot-api'
SDKをつかった場合lib/line_client.rbが以下のような感じになるかと思います.
require 'line/bot'
class LineClient
CHANNEL_SECRET = ENV['CHANNEL_SECRET']
CHANNEL_ACCESS_TOKEN = ENV['CHANNEL_ACCESS_TOKEN']
PUSH_TO_ID = ENV['PUSH_TO_ID']
attr_reader :client
def initialize
@client ||= Line::Bot::Client.new { |config|
config.channel_secret = CHANNEL_SECRET
config.channel_token = CHANNEL_ACCESS_TOKEN
}
end
def reply(reply_token, message)
client.reply_message(reply_token, text_message(message))
end
def push(message)
client.push_message(PUSH_TO_ID, text_message(message))
end
private
def text_message(text)
{
"type" => "text",
"text" => text
}
end
end
※1 line_client.rbってなんだという方はアカウント発行などの下準備のあたりで紹介したqiitaの記事を参照してください.
※2 LineClient#pushはPush Message APIを使用する際に使います.PUSH_TO_IDは後でもう少し詳しく説明します.
ControllerとかServiceとかその辺の設定
あとでHerokuのSchedulerというrakeタスクを定期実行するAddOnを使用してリマインダーを設定するのですがその前にControllerからServiceまでの設定を一気に整えてしまいましょう.リマインダーを設定する際はHeroku側のTZ環境変数をAsia/Tokyoに変更しておくこともお忘れなく.この辺は我が家の設定なので適宜読み飛ばしてください.
重要なのは次のSchedulerの設定とPush先のIDの取得方法です.
以下で家賃のリマインド,掃除のリマインド,ゴミ出しのリマインドを設定しています.
class WebhookController < ApplicationController
protect_from_forgery with: :null_session
def reminder
reminders = get_reminders
logger.info(reminders)
reminders.each do |reminder|
res = line_client.push(reminder)
logger.info(res.body)
end
end
private
def get_reminders
reminder_service.execute
end
def reminder_service
ReminderService.new
end
def line_client
@line_client ||= LineClient.new
end
end
require 'date'
require_relative '../../config/initializers/constants'
class ReminderService
def initialize(today = Date::today, garbage_service = GarbageService.new, cleaning_date_service = CleaningDateService.new)
@today = today
@garbage_service = garbage_service
@cleaning_date_service = cleaning_date_service
end
def execute
check_reminders
end
def check_reminders
reminders = []
garbage_reminder = check_garbage_reminder
payment_reminder = check_payment_reminder
cleaning_date_reminder = check_cleaning_date_reminder
reminders << garbage_reminder if garbage_reminder.present?
reminders << payment_reminder if payment_reminder.present?
reminders << cleaning_date_reminder if cleaning_date_reminder.present?
reminders
end
def check_garbage_reminder
@garbage_service.reminder(@today)
end
def check_payment_reminder
if @today.day == 25
"今日は25日です. 家賃の振り込みをおねシャッス!口座は以下です\n#{Constants::ACCOUNT}"
elsif @today.day == 28
'今日は28日です. まさかまだ家賃振り込んでない人なんていないよね...'
else
''
end
end
def check_cleaning_date_reminder
@cleaning_date_service.reminder(@today)
end
end
require 'date'
require_relative '../models/garbage'
require_relative '../../config/initializers/constants'
class GarbageService
def initialize(garbage_model = Garbage)
@garbage_model = garbage_model
end
def reminder(today)
# 0 = 日曜日, 6 = 土曜日
day_of_week = today.wday
week_of_month = get_week_of_month(today)
case day_of_week
when 0
set_person_in_charge
"明日からのゴミ出し大臣は#{get_person_in_charge}さんです! よろしくお願いします!"
when 1
if week_of_month == 2 || week_of_month == 4
"今日は不燃ゴミの日です. ゴミ出し大臣の#{get_person_in_charge}さんは不燃ゴミをお願いします!"
end
when 2, 5
"今日は燃えるゴミの日です. ゴミ出し大臣の#{get_person_in_charge}さんは燃えるゴミをお願いします!"
when 6
"今日は資源ごみの日です, ゴミ出し大臣の#{get_person_in_charge}さんはカン・ビン・ペットボトル・ダンボールのゴミ出しをお願いします!"
else
''
end
end
def get_person_in_charge
@garbage_model.last.name
end
def set_person_in_charge_manually(name)
if Constants::MEMBER_LIST.include?(name)
@garbage_model.create(name: name)
"今週のゴミ出し大臣を#{name}さんに設定しました"
else
"名前が不正です. セットすることができる名前はこちら#{Constants::MEMBER_LIST.join(', ')}"
end
end
private
def set_person_in_charge
list = Constants::MEMBER_LIST
previous = get_person_in_charge
if previous == list.last
@garbage_model.create(name: list.first)
else
next_index = list.index(previous) + 1
@garbage_model.create(name: list[next_index])
end
end
def get_week_of_month(today)
(today.day + 6) / 7
end
end
require 'date'
require_relative '../models/cleaning_date'
class CleaningDateService
def initialize(cleaning_date_model = CleaningDate)
@cleaning_date_model = cleaning_date_model
end
def reminder(today)
if scheduled_cleaning_date - today == 3
return "次の掃除は#{scheduled_cleaning_date.strftime("%m/%d")}です.忘れないようにしましょう.参加できない場合は代わりに1000円ですよ~"
elsif scheduled_cleaning_date == today
return '今日は掃除の日です.参加できない場合は代わりに1000円ですよ~'
end
if scheduled_cleaning_date.month != today.month && scheduled_cleaning_date.month != today.month + 1
case today.day
when 1
'月初なので, 今月の掃除日を決めましょう!'
when 5, 10, 20
'まだ今月の掃除日が決まっていないみたいです... 今日中に必ず決めましょう!'
when 29
'今月掃除してないです... おこですよ!!'
else
''
end
else
''
end
end
def scheduled_cleaning_date
@cleaning_date_model.last.cleaning_date
end
def scheduled_cleaning_date=(date)
@cleaning_date_model.create(cleaning_date: date)
end
end
Heroku Schedulerの設定
上記のリマインダーを定期実行するためのrakeタスク化してします.さらにそれをHerokuのSchedulerというAddOnで毎朝定期実行していきます.1. rakeタスクの登録
lib/tasks以下に以下のように,WebhookController#reminderを実行するrakeタスクを作成します.
task :reminder_task => :environment do
webhook_controller = WebhookController.new
webhook_controller.reminder
end
2. Heroku Schedulerの設定
Heroku SchedulerというAddOnをHerokuに追加します.追加したら以下のようにreminder_taskを登録します.時間はUTCでしか設定できないようです.自分は毎朝日本時間の朝6時半にリマインドしてほしかったので21:30 UTCに時間を設定しています.
Push先のIDの取得
ふう..これでやっと最後です.
line_client.rbの中にPUSH_TO_ID
というのがあったと思いますがこれを指定することで,毎朝決まったグループ/個人(我が家の場合は住人が入っているグループのID)にメッセージを送信します.
このIDの取得方法のベストプラクティスがまだわからないのですが,基本的にはLineAPIからのリクエストの中に入っているsource: {groupId: xxxxx}
(個人の場合はuserId)を使用すれば問題ないようです.リクエスト自体はオウム返し系の記事で書いてあるようにcallbackの口をコントローラーに作って上げれば受け取れるかと思います.
最後に
ここでは本題からずれるのであまり書きませんでしたが,実際にボットを使っていると結構手動で直したいときがありました(今週のゴミ出し担当を手動で設定したいとか).なので実はそのへんの口もちゃんと用意してあります.
ソースコードは以下に公開しているので,気になる方は確認して見てください.
https://github.com/shuheiktgw/tombot