まだ、導入編をやっていない方は【導入編】hubotでチームだけのお手軽オリジナルslackリマインダーを作るをやってください。
寒空の朝昔の女の夢を見た。その時、朝日が股間を照らしていた。
さて今日は、hubot企画の実装編です。
今回は前回の予告通り、 cronを使って定期投稿 と railsアプリケーションのapiを叩く の二点を主題にして進めていきます。
手順
- 導入編で作成したhubotを持参してください_ø(●ʘ╻ʘ●)
- railsで簡単なapiを作ります(※すでにチームのプロジェクトがある方はそれを使ったほうが楽です)
- チームのプロジェクトを持っている方はrailsのslack-apiというgemを使って、チームのメンバーのidやe-mailを取得すると、連携やメンションが簡単にできます。
- hubotからapiを叩いてデータを取得します
- 定時にそのデータを元に投稿します
事前準備
まずは今回使用するrailsアプリケーションを作ります。ここでは手順だけ記述し、説明は割愛させていただきます。また、すでにアプリケーションを持っている方で、そこにapi作るよって方はこの章は飛ばしていただいて大丈夫です。
データベース作成
# データベースはmysqlを使っているので、-dオプションでmysqlを指定
$ rails new hubot-api-app -d mysql
$ cd hubot-api-app
$ rake db:create
$ rails g model user family_name:string first_name:string
$ rails g model shift date:date start_time:integer
$ rails g model shift_user user:references shift:references status:integer
$ rake db:migrate
モデルの関連付け
class User < ActiveRecord::Base
has_many :shift_users
has_many :shifts, through: :shift_users
end
class Shift < ActiveRecord::Base
has_many :users, through: :shift_users
has_many :shift_users
end
class ShiftUser < ActiveRecord::Base
belongs_to :user
belongs_to :shift
enum status: %i(not_in_charge in_charge manager)
end
shfit_user.rbに関してはgenerateの時にreferencesで定義をしてあるので、すでにuserとshiftに対する関連付けがされています。
必要なgemを追加
Gemfileに以下を追記します。
gem 'gimei'
bundle install
コマンドを実行。
データベースに初期値を設定
seeds.rbファイルをダウンロードし、データベースに初期データを入れます。
$ rm db/seeds.rb
$ wget db/ https://github.com/saino-katsutoshi/hubot-api-app/raw/master/db/seeds.rb
$ rake db:seed
データベース設計について軽く言及します。
テーブル名 | カラム名 | 役割 |
---|---|---|
shifts | date | シフトの日付 |
start_time | シフト開始時刻 | |
users | family_name | 姓 |
first_name | 名 | |
shift_users | user_id | usersテーブルへの外部キー |
shift_id | shiftsテーブルへの外部キー | |
status | 0:担当でない 1:担当 2:責任者 |
コントローラーを作成
明日のシフトを取得するコントローラーを作成
$ rails g controller api/shifts/tomorrow
class Api::Shifts::TomorrowController < ApplicationController
def index
@users = []
tomorrow_shifts = Shift.includes(:users).where(date: Date.tomorrow)
i = 11
tomorrow_shifts.each do |shift|
@users << { start_time: i , in_charge: shift.users.where(shift_users:{status: 1}), manager: shift.users.where(shift_users:{status: 2}) }
i += 2
end
end
end
json.array! @users do |item|
json.start_time item[:start_time]
json.in_charge do
json.array! item[:in_charge] do |in_charge|
json.id in_charge.id
json.full_name in_charge.family_name + in_charge.first_name
end
end
json.manager do
json.array! item[:manager] do |manager|
json.id manager.id
json.full_name manager.family_name + manager.first_name
end
end
end
Rails.application.routes.draw do
namespace :api, defaults: {format: :json} do
namespace :shifts do
resources :tomorrow, only: :index
end
end
end
heroku
これからapiをherokuにアップします。
この章ではherokuとsequelProを使います。
本記事ではmysqlを使います。sqLite3をお使いの方は以下のサイトを参考に登録してください。
herokuにアップする
$ heroku login
# 登録したe-mailとパスワードを入力
$ git init
$ git add .
$ git commit -m 'initial commit'
$ heroku create hubot-api-app
左が作成されたアプリケーションのURL、右がgitのURLです。
データベースをmysqlに変更する
$ heroku addons:add cleardb
$ heroku config | grep CLEARDB_DATABASE_URL
出力: CLEARDB_DATABASE_URL: mysql://×××××××××××××××××××××
mysql2gem
を使っているのでデータベースのURLもmysqlからmysql2に変更します。
$ heroku config:set DATABASE_URL=mysql2://×××××××××××××××××××××
$ heroku config
DATABASE_URL: mysql2://×××××××××××××××××××××
これは、以下のようになっています。
mysql2://ユーザー名:パスワード@ホスト名/データベース?reconnect=true
sequelProでデータベースにアクセスします。お気に入りに追加ボタンを押しておくと、次からアクセスするのにサイド入力する必要がなくなります。
これでデータベースへのアクセスができました。
herokuのデータベースに初期値を入れる
$ heroku run rake db:migrate
$ heroku run rake db:seed
これでapiの準備が完了しました。これまででエラーなどでつまづいた方はコメントにて僕に連絡してください。
apiをhubotに叩かせる
やっとこれから本題に入ります。
前章までに作ったapiをhubotから叩くことで、json型のデータを取得し、それを利用してリマインダーを作ります。また、次の章でそれを定期投稿できるようにします。
hubotスクリプトの基礎を抑える
まず、hubotスクリプトで良く使うメソッドの一覧表と使用例をまとめておきます。
hubotスクリプトの基本的な関数と使い方
関数 | 仕様 | 結果 |
---|---|---|
respond | 呼ばれたら反応する | |
hear | チャンネル上のメッセージに反応する | |
enter | チャンネルに入室した際に反応する | |
leave | チャンネルを退出した際に反応する | |
topic | トピックが立てられたら反応する | |
send | メッセージを送信する | |
reply | 発言者に対して返信する | |
random | 引数の配列からランダムに取り出す |
プロパティ | 仕様 | 結果 |
---|---|---|
match | respondやhearで正規表現でマッチしたものを取り出すときに使います | |
message | 送られてきたメッセージに関する情報を取得できます |
使用例集
module.exports = (robot) ->
robot.respond /疲れた/i, (msg) ->
msg.send "あとちょっと、頑張って♡"
module.exports = (robot) ->
robot.hear /休憩/i, (msg) ->
msg.send "お疲れ様!”
module.exports = (robot) ->
robot.hear /誰か(.+)/i, (msg) ->
msg.reply "#{msg.match[1]}?どした?"
module.exports = (robot) ->
robot.enter (msg) ->
msg.send "いらっしゃい!#{msg.message.user.name}さん!"
robot.leave (msg) ->
msg.send "#{msg.message.user.name}さん ばいばい、、、"
module.exports = (robot) ->
robot.topic (msg) ->
msg.reply "トピックが立ったぞい!"
module.exports = (robot) ->
robot.enter (msg) ->
msg.send "いらっしゃい!#{msg.message.user.name}さん!"
module.exorts = (robot) ->
words = [
'大吉',
'中吉',
'吉',
'大凶'
]
robot.respond /おみくじ/i, (msg) ->
msg.send msg.random words
module.exports = (robot) ->
robot.respond /(.+)駅/, (msg) ->
msg.send "#{msg.match[0]}"
msg.send "#{msg.match[1]}"
module.exports = (robot) ->
robot.respond /こんにちは/, (msg) ->
msg.send "@#{msg.message.user.name}さん、こんにちは"
msg.send "このチャンネルは ##{msg.message.room} です"
# messageによって得られるデータは以下のようになってます。
message: {
user: { id:'user', name:'saino', room:'#general' },
text: 'こんにちは',
id: undefined,
done: false,
room: '#general'
},
メンションのつけ方
メンションのつけ方はいたって簡単で、@channelとしたかったら とするだけです。
以下表にまとめました。特定のユーザーにメンションを飛ばす場合は、単に文字列に @id とするだけでメンションがつきます。
入力 | 出力 |
---|---|
@channel | |
@here | |
@everyone | |
@id | @id |
httpメソッドでhubotからapiを叩く
まずは、簡単にこちらからのメッセージに対して反応させるようにします。
module.exports = (robot) ->
robot.respond /hi/i, (msg) ->
msg.send 'Hi'
するともちろん以下のようになります。
Hubot> Hubot hi
Hubot> Hi
これでとりあえず反応してくれるようになったので、次はapiを叩きます。
module.exports = (robot) ->
robot.resopnd /hi/i, (msg) ->
request = msg.http('http://hubot-api-app.herokuapp.com/api/shifts/tomorrow').get()
msgオブジェクトの httpメソッドは、引数のurlにHTTP通信をします。
getメソッドは、お察しの通りHTTPメソッドです。
queryメソッドを使えば、パラメーターも送ることができます。使用例は以下の通りです。
module.exports = (robot) ->
robot.respond /hi (.*)/i, (msg) ->
keyword = msg.match[1]
msg.http('https://xxxx/')
.query(q: keyword)
.get()
これで、変数requestにapiを叩いた結果のデータを格納することができました。ではこれからレスポンスとして返されたjsonデータから具体的なデータをメッセージとして送ります。
レスポンスは以下のような形になってます。(これらの人物はおそらく実在しません。)
[
{
"start_time":11,
"in_charge":[{"id":162,"full_name":"野口政美"},{"id":192,"full_name":"岡田春風"},{"id":92,"full_name":"丸山泰弘"}],
"manager":[{"id":122,"full_name":"吉沢徳男"}]
},
{
"start_time":13,
"in_charge":[{"id":182,"full_name":"吉沢紗愛"},{"id":22,"full_name":"栗田実音"},{"id":242,"full_name":"榎本晴歌"}],
"manager":[{"id":232,"full_name":"戸田倖"}]
},
{
"start_time":15,
"in_charge":[{"id":132,"full_name":"荻野采弓"},{"id":42,"full_name":"大山桃央"},{"id":72,"full_name":"佐々木清司"}],
"manager":[{"id":52,"full_name":"中野紫稲"}]
},
{
"start_time":17,
"in_charge":[{"id":42,"full_name":"大山桃央"},{"id":2,"full_name":"大平朝水"},{"id":62,"full_name":"小山悠莉"}],
"manager":[{"id":182,"full_name":"吉沢紗愛"}]
}
]
weekDayJP = ['日', '月', '火', '水', '木', '金', '土']
module.exports = (robot) ->
robot.respond /give me (.*) shift/i, (msg) ->
nObj = new Date
month = nObj.getMonth() + 1
date = nObj.getDate() + 1
day = weekDayJP[nObj.getDay()]
tomorrow = "#{month}月#{date}日(#{day})"
request = msg.http('http://hubot-api-app.herokuapp.com/api/shifts/' + msg.match[1])
.get()
request (err, res, body) ->
data = JSON.parse body
message = ''
for value in data
message += "【#{value.start_time.toString()}時】\n"
message += "責任者:"
for v2 in value.manager
message += "#{v2.full_name} "
message += "\n"
for v1 in value.in_charge
message += "#{v1.full_name} "
message += "\n"
msg.send "#{tomorrow}のシフトは、、、、、\n#{message}"
(.*)の部分をキャプチャし、それをhttpメソッドのパスに渡すことでtomorrowやtodayに対応します。todayなどやりたい場合は、apiを追加しないといけません。
変数requestにはレスポンスの情報が格納されています。
bodyはapiからのレスポンスがテキストの形になっています。JSON.parse
の引数にこれを指定してあげると、テキストをjsonに解析してくれます。
それ以下は単にjsonからデータを取得しています。
このあたりをslack-apiでチームのメンバーのidなどを取得することで、メンションなどつけることができます。今回は分量が多くなってしまうので、省略します。
これで、こちらのメッセージに反応して明日のシフトを送ってくれるようになりました。では最後にこれを定刻に投稿してくれるようにします。
cronを使って定刻に投稿する
hubotのディレクトリで以下のコマンドを実行する。
$ npm install --save cron
$ npm install --save time
あとは、cronを設定するだけです。
cronJob = require('cron').CronJob
module.exports = (robot) ->
cronJob = new cronJob('* * * * * *', () ->
envelope = room: "#general" #投稿するルーム指定
robot.send envelope, "cron動いてるよ" #投稿メッセージ
)
cronJob.start() #jobを開始、startがないと永遠に実行されません。
# cronJob.stop() #jobを停止
時間を設定する
第一引数にcronの時間を設定します。全部で6つ指定します。左から、
- 秒: 0 ~ 59
- 分: 0 ~ 59
- 時: 0 ~ 23
- 日付: 1 ~ 31
- 月: 0 ~ 11
- 曜日: 0 ~ 6(0:日, 1:月, 2:火, 3:水, 4:木, 5:金, 6:土)
と設定できます。指定しない場合は *
を指定します。
いくつか例を挙げます。
//毎秒実行する、うるさいので非推奨
new cronJob('* * * * * *', () ->
//30秒毎に実行する、メンバーに不快感を与えますので非推奨
new cronJob('*/30 * * * * *', () ->
//毎分実行する
new cronJob('00 * * * * *', () ->
//月曜朝9時に実行する
new cronJob('00 00 09 * * 1', () ->
//平日の9時に実行する
new cronJob('00 00 09 * * 1-5', () ->
これを使って、reminder.coffeeを以下のように毎日19時に実行するようにします。
cronJob = require('cron').CronJob
weekDayJP = ['日', '月', '火', '水', '木', '金', '土']
module.exports = (robot) ->
postTomorrowShift = new cronJob('00 00 19 * * 0-6', () ->
envelope = room: "#general"
nObj = new Date
month = nObj.getMonth() + 1
date = nObj.getDate() + 1
day = weekDayJP[nObj.getDay()]
tomorrow = "#{month}月#{date}日(#{day})"
request = robot.http('http://hubot-api-app.herokuapp.com/api/shifts/tomorrow')
.get()
request (err, res, body) ->
data = JSON.parse body
message = ''
for value in data
message += "【#{value.start_time.toString()}時】\n"
message += "責任者:"
for v2 in value.manager
message += "#{v2.full_name} "
message += "\n"
for v1 in value.in_charge
message += "#{v1.full_name} "
message += "\n"
robot.send envelope, "#{tomorrow}のシフトは、、、、、\n#{message}"
postTomorrowShift.start()
robotがこちらに反応するのではなく、自らが行うので、robotオブジェクトのhttpメソッドとsendメソッドに変更しました。結果は同じです。
これで以上です。
終わり
駆け足で作ってしまったので、リファクタリングや修正などありましたら是非コメントください。_ø(●ʘ╻ʘ●)
また、今回はtomorrowだけでしたが、これをちょっといじれば結構応用効くと思いますので、是非日曜プログラミングでチームのhubotを作ってみてください。