slackbotでRails製websocketサービスを叩いて非同期描画してみる


背景

春になり、新入生が自分の大学にも入ってきました。
今年度から自分が運営している団体も本格的に人を集めようと思い、気づいた時には40人ものニューフェイスが参加表明をしてくれました。

今日は、その人たちの為に作ったwebsocketサービスとslack botの連携について少しまとめられたらなと思います。

作ったもの

僕たちの団体に所属する人には、タイポでのバグ等最低限のミスをなくすために、「寿司打」というタイピングゲームの高級コースで一万点以上のスコアを取ることを課しています。

はじめは壁に書いていたのですが、人数が増えてきておさまらなくなってきたので、じゃあWebに置き換えるついでに表題という感じです。

はじめは上のように更新したらマーカーで書き換えてましたが、

いまは上記のようにWeb上で管理できています。

slack上で、当団体の電脳ペットである「taro」に申告することで、自動で順位を計算し描画しなおしてくれます。

websocketを使っているので、更新ボタンを押さなくても、自動的に画面が切り替わってくれるのが嬉しいところです。

実装方法

オーバースペックかもしれませんが、websocket-railsを使いました。
websocket-railsの使い方については他に譲るとして、実際にどのように通信したのかを説明します。実装方法としては、/websocketにリクエストが走ると、client_connectedというイベントが呼ばれるので、curlでそのURLを叩き処理を差し込んであげました。具体的には下記です。

websocket_sushida_controller.rb
class WebsocketSushidaController < WebsocketRails::BaseController
  def initialize_session
    puts "Session Initialized\n"
  end

  def client_connected #アクセスされると呼ばれる
    puts "connected"
    if params[:name]
      if @user = User.find_or_create_by(name: params[:name])
        if params[:screen_name]
          @user.update(screen_name: params[:screen_name])
        else
          @score = Score.create(value: params[:score].to_i)
          @score.user = @user
          @score.save()
        end
      end
    end

    @users = User.all.map do |user|
      @data = user.attributes
      if @score = user.scores.last
        @data[:score] = @score.value
      else
        @data[:score] = 0
      end
      @data
    end
    @users = @users.sort_by{|user| user[:score]}.reverse
    broadcast_message :new_message, @users
  end
end

huebotの方は以下の通りです

sushida.coffee
module.exports = (robot) ->
  execSync = require('child_process').execSync
  loadsh = require('lodash');

  robot.respond /sushida score (\S+)$/, (msg) ->
    if msg.match[1].match(/[0-9]+/)
      score = msg.match[1]
      update_score msg, msg.envelope.user.name, score
    else
      msg.send "変な値入れないでよ"

  robot.respond /sushida name (\S+)$/, (msg) ->
    if msg.match[1]
      screen_name = msg.match[1]
      update_name msg, msg.envelope.user.name, screen_name
    else
      msg.send "変な値入れないでよ"

  update_score = (msg, name, score) ->
    execSync 'timeout -sKILL 1 curl -X GET "http://example.com/websocket?' +"name=#{name}&score=#{score}\""
    msg.send "#{name}さんの寿司打のスコアを更新したよ。#{score}円分も食べたんだね!すごい!!"
  update_name = (msg, name, screen_name) ->
    execSync 'timeout -sKILL 1 curl -X GET "http://example.com/websocket?' +"name=#{name}&screen_name=#{screen_name}\""
    msg.send "#{name}さんの表示名を#{screen_name}に変えたよ" 

taroには、クエリパラメータ付きのGETリクエストでアクセスしてもらっています。
/websocket?query=value
とすれば、controllerの方でparams[:query]のデータを取り扱うことができます。
破壊的で、実際にサービスとして運用するのは難しいと思いますが、内部向けのトイサービスなのでこれで良しとしました。

一つ注意する点は、一度/websocketにアクセスすると、コネクションを貼ってしまって、そのままhuebotが固まってしまいます。なので、timeoutを使い一秒で処理を強制終了させています。

暴力的ですが、簡単に実装ができるためお勧めです。
誰かの参考になればと思います。