Yuhei Okazaki
仕様
Slackで /nicole
というコマンドが使えるようになります。
コマンドは以下の形式で入力できます。
/nicole [good | normal | bad] [今日の一言]
例:
/nicole good 今日は給料日なので気分が良い!
/nicole normal 普通です。
/nicole bad 朝から頭が痛いので「悪い」です。。。
例えば
このように入力すると
このように登録されます。
インフラ構成
Slackからの通知を受けるエンドポイントをAWS SAMで構築しています。
最終的に入力内容をSQSに登録している点がPOINTです。
Nicoleは社内システムのためIP制限がかかっており、エンドポイントを作ってもSlackからアクセスができません。 このため、AWS SAMで作ったエンドポイント(Api Gateway)→Lambda→SQSという順番でデータを流し、Nicole側からSQSをポーリングしています。
アプリケーション
クラウド側(Lambda)
Slackから送信されたデータは lambda_handler
の evnet['body']
に格納されています。これはURL-encoded form dataなので、デコードが必要です。
デコードが完了したら token
が正しいものであるかチェックした後、コマンド文字列をパースして入力内容をJSON化します。
JSONをSQSへsendしたらLambdaの処理は完了です。
require 'uri'
require 'date'
require 'json'
require 'aws-sdk'
MOOD_REGEX = /\A[a-z]*/
def lambda_handler(event:, context:)
params = Hash[URI::decode_www_form(event['body'])]
return result(404) unless params['token'] == ENV['SLACK_TOKEN']
return result(200, '気分が未入力です') if params['text'].nil?
slack_user, date, mood, message = parse_params(params)
return result(200, '気分が正しくありません') unless ['good', 'normal', 'bad'].include?(mood)
begin
send_niko(slack_user, date, mood, message)
rescue Aws::SQS::Errors::NonExistentQueue => e
return result(200, "登録に失敗しました\n#{e}")
end
result(200, "[ 本日の気分: #{mood}, 一言: #{message} ] を登録しました。良い一日を!")
end
def result(status, message='')
{ statusCode: status, body: message }
end
def parse_params(params)
mood = params['text'].slice(MOOD_REGEX)
message = params['text'].gsub(MOOD_REGEX, '').gsub(/\A\s/, '')
slack_user = params['user_name']
date = Date.today.strftime('%Y-%m-%d')
message ||= '未入力'
[slack_user, date, mood, message]
end
def send_niko(slack_user, date, mood, message)
sqs = Aws::SQS::Client.new(region: 'ap-northeast-1')
send_message_result = sqs.send_message({
queue_url: sqs.get_queue_url(queue_name: 'nicole-stack-app-CreateNikoQueue-xxxxxxxxxxxx').queue_url,
message_body: { slack_user: slack_user, date: date, mood: mood, message: message }.to_json,
})
end
社内システム側(Shoryuken)
NicoleはRailsで実装されています。RailsからSQSを簡単にポーリングするために ShoryukenというGemを使いました。
ActiveJobのようにSQSからデータをreceiveしたときの処理を記述できます。
class CreateNikoJob < ApplicationJob
include Shoryuken::Worker
shoryuken_options queue: Rails.application.credentials.aws[:queue_name],
auto_delete: true,
body_parser: :json,
batch: true
def perform(_message, json)
json.each do |record|
user = User.find_by(slack_user: record['slack_user'])
next if user.nil?
niko = user.nikos.find_or_initialize_by(date: Time.zone.parse(record['date']))
niko.mood = record['mood'].to_sym
niko.message = record['message']
if niko.new_record?
niko.niko_histories.build(user: user, action: :create_niko)
niko.save!
notify_niko(niko)
else
niko.niko_histories.build(user: user, action: :update_niko)
niko.save!
end
end
end
private
def notify_niko(niko)
notifier.post(
text: "気分が投稿されました",
attachments: [niko_format(niko)]
)
end
def notifier
Slack::Notifier.new(webhook_url)
end
def webhook_url
Rails.application.credentials.send(Rails.env)[:slack_webhook_url]
end
def niko_format(niko)
{
color: '#FFBE0B',
author_name: niko.user.name,
author_link: Rails.application.routes.url_helpers.user_url(niko.user),
title: "#{I18n.l(niko.date.to_date)}の気分: #{niko.mood_i18n}",
text: niko.message + "\n" + Rails.application.routes.url_helpers.niko_url(niko)
}
end
end
まとめ
IP制限付きの社内システムはSlack Appに対応することが難しいと思っていましたが、実際に作ってみると簡単に実現できました。小さめのAPIを作るのはやっぱりAWS SAMが便利ですね。
Fusicには他にも運用中の社内システムが多数あるので、いろいろな操作をSlackでできるようになると便利だなと思いました。