はらへり日記

腹に弾丸

hubot choiceに変数機能をつけた

hubot choiceとは

名前の通り、与えられた引数の中から1つをランダムにchoiceするスクリプトです。

大学の先輩がゼミ用に実装していたものを拝借し、会社でコードレビュアーを決めるために導入していました。

masuilab/slack-hubot

実装は至極シンプルなので結構便利に使ってました。

※画像はイメージです

問題

しかし、シンプルなchoiceのままだとコードレビュアーの決定に使用するにはいくつか問題がありました。

  • 毎回、レビュアー候補を入力しなければならず、めんどくさい

  • もし名前をtypoするとnotificationが本人に飛ばない

これらを解決するため、hubot choiceを少しだけ改良することにしました。

解決策

使用者の大半はエンジニアであることと、用途のほとんどはレビュアーの決定であることを加味して以下の様な実装にすることにしました。

  • hubotがグループ名とグループの要素を変数ライクに記憶できるようにする

  • 設定したグループ名をhubot choiceの引数に持ってくることで設定された要素からchoiceする

  • グループ名か否かの判別は$がついてるか否かで判別する(弊社はPHPerの会社なので…)

実装

グループ名の記憶にはhubot brainを使用し、シンプルにsetdeleteコマンドを用意する。

以下実装コード。

# Description
#   1つランダムに選ぶ
#
# Commands:
#   hubot choice ほげ もげ ふが -- 引数からランダムにchoice
#   hubot choice $<groupname> -- 登録されたグループの要素の中からランダムにchoice
#   hubot choice set <group name> <group elements> -- グループを設定
#   hubot choice delete <group name> -- グループを削除
#   hubot dump -- 登録されているグループ一覧を表示
#
# Author:
#   @sota1235
#
# Thanks:
#   https://github.com/masuilab/slack-hubot/blob/master/scripts/choice.coffee

_ = require 'lodash'

module.exports = (robot) ->
  CHOICE = 'choice_data'

  # データ取得
  getData = () ->
    data = robot.brain.get(CHOICE) or {}
    return data

  # データセット
  setData = (data) ->
    robot.brain.set CHOICE, data

  # グループをセット
  setGroup = (groupName, groupElement) ->
    data = getData()
    data[groupName] = groupElement
    setData(data)
    return

  # グループを削除
  deleteGroup = (groupName) ->
    data = getData()
    if data[groupName] is undefined
      return false
    delete data[groupName]
    return true

  # グループ要素を取得
  getGroupElem = (groupName) ->
    data = getData()
    if data[groupName] is undefined
      return false
    else
      return data[groupName]

  robot.respond /choice (.+)/i, (msg) ->
    items = msg.match[1].split(/\s+/)
    head  = items[0] # for judge command is choice or not

    # set, dump,deleteの場合、return
    if head is 'set' or head is 'dump' or head is 'delete'
      return

    # 第一引数がグループ名指定の場合
    if /\$(.+)/.test items[0]
      items = getGroupElem items[0].substring(1)
      if not items
        msg.send "無効なグループ名です"
        return

    choice = _.sample items
    msg.send "厳正な抽選の結果、「#{choice}」に決まりました"

  # グループを設定
  robot.respond /choice set (.+)/i, (msg) ->
    items = msg.match[1].split(/\s+/)
    groupName    = items[0]
    items.shift()
    groupElement = items
    setGroup groupName, groupElement
    msg.send "グループ:#{groupName}を設定しました"

  # グループを削除
  robot.respond /choice delete (.+)/i, (msg) ->
    groupName = msg.match[1].split(/\s+/)[0]
    if deleteGroup groupName
      msg.send "グループ:#{groupName}を削除しました。"
    else
      msg.send "グループ:#{groupName}は存在しません。"

  # for debug
  robot.respond /choice dump/i, (msg) ->
    data = getData()
    if _.size(data) is 0
      msg.send "現在登録されているグループはありません"
      return
    for gname, gelm of data
      msg.send "#{gname}: #{gelm.join()}"

ロジックに関して特筆すべき点はないと思います。

個人的にはlodashsize()関数が便利すぎて泣きました。

機能一覧

以下の4つの機能が実装されています。

ランダム選択

コマンド:hubot choice hoge moge fuga or hubot choice $<groupename>

従来のchoice機能。引数からランダムにchoice。$が付いている場合は該当のグループ内からランダムにchoice。

グループ追加

コマンド:choice set <groupname> hoge moge fuga

グループとその要素をセット。例えばchoice set hubot-team cat dog birdと打つとhubot-teamグループにcatdogbirdがセットされる。

使用するときはhubot choice $hubot-teamとすることでcatdogbirdからランダムにchoiceされる。

グループ削除

コマンド:choice delete <groupname>

グループを削除する。

グループ一覧表示

コマンド:choice dump

デバッグ用。現在登録されているグループとその要素を一覧で表示する。

使い方

こんな感じ(プライバシーを配慮して僕以外のユーザ名は伏せられています)。

f:id:sota1235:20150615000212p:plain

まとめ

  • hubot choiceを拡張する変数機能つけた

  • チーム開発でレビュアーをランダムに素早く決めるために作った

その他

バグや不具合を見つけたらsota1235/slack-fresh15-hubot : issuesにマサカリ投げてください(╹◡╹)