dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

Slackのチャンネルのメンバーからランダムで一人選ぶBotを作る

タイトルの通り。

  • GitHub の PR のレビュアーを、開発チームの特定のメンバー数名から一人指名したい
  • 開発チームの一部のメンバーで持ち回りでやらないといけないタスクがあって、毎回誰がやるか決めるのめんどい

といったことがあって、せっかくなので bot に決めてもらえないかなあという気分になりました。

「どの人たちから選ぶか」は目的によってまちまちなので、それなら Slack のチャンネルのメンバーから選ぶようにすれば汎用性が高いんじゃないかと思って作ってみました。

コード

先にコードから。

なお、フレームワークとして Hubot を使ってますが、CoffeeScript を書くのが嫌だったので ES2015 で書いてます。
そのあたりの設定の話はこちらを参考にしてください。

dackdive.hateblo.jp

import request from 'request';

module.exports = (robot) => {
  robot.respond(/.*(random|抽選|選ぶ).*/, (msg) => {
    const url = 'https://slack.com/api/channels.list?token=' + process.env.HUBOT_SLACK_TOKEN;

    // チャンネル一覧を取得
    request(url, (err, res, body) => {
      // msg.message.room で現在の channel 名が取れる
      const channel = findChannel(JSON.parse(body).channels, msg.message.room);
      console.log('Channel found');
      console.log(channel);

      // bot 自身を除外して抽選
      const botId = robot.adapter.self.id;
      const filterdMembers = channel.members.filter((member) => {
        return member !== botId;
      });
      console.log(filterdMembers);
      const member = msg.random(filterdMembers);

      msg.send('選ばれたのは、 <@' + member + '> でした :tea:');
    });
  });
}

function findChannel(channels, targetName) {
  for (const channel of channels) {
    if (channel.name === targetName) {
      return channel;
    }
  }
  return null;
};

GitHub:

(PR は #1


動作の様子

f:id:dackdive:20160728184414p:plain

bot に向かって random/抽選/選ぶ といった単語を含めてメンションすると適当に選んでくれます。


実装上のポイントなど

現在のチャンネル名は msg.message.room で取れる

最初に苦労したのは、自分が今いるチャンネルを取得するという処理です。
以下の記事を読んで msg.message.room で取れるんだーということを知りました。

The Hubot msg Object

自分でも console.log(msg) して中身を確認してみました。
message 以外にも robot に色々情報が入ってそうです。

Response {
  robot:
   Robot {
     name: 'hubot',
     events:
      EventEmitter {
        ...
        _maxListeners: undefined },
     brain:
      Brain {
        ...
        _eventsCount: 3 },
     alias: false,
     adapter:
      SlackBot {
        customMessage: [Function],
        message: [Function],
        ...
  message:
   SlackTextMessage {
     user:
      User {
        id: 'U1R73F7DL',
        name: 'zaki-yama',
        ...
        room: 'bot-channel' },
     text: '@hubot: random',
     rawText: '<@U1R7NPEKU>: random',
     rawMessage:
      Message {
        _client: [Object],
        _onDeleteMessage: [Function],
        deleteMessage: [Function],
        _onUpdateMessage: [Function],
        updateMessage: [Function],
        type: 'message',
        channel: 'C1VTH4FPD',
        user: 'U1R73F7DL',
        text: '<@U1R7NPEKU>: random',
        ts: '1469699396.000062',
        team: 'T1R7BJHV3' },
     id: '1469699396.000062',
     done: false,
     room: 'bot-channel' },


Slack の channel オブジェクトからチャンネルに所属するメンバーを取得する

Slack APIchannels.info でチャンネル一覧を取得し、チャンネル名が msg.message.room と一致するものを探します。

レスポンスの channels プロパティの中にすべてのチャンネルが配列で入ってます。
また、channel オブジェクトの中身はこちらに記載されています。
https://api.slack.com/types/channel

channel.members でメンバーの一覧が取得できますね。


Bot 自身の情報は robot.adapter.self.id で取れる

Bot もチャンネルに含まれているため、ランダムで一人選ぶときには自分を除外しなければなりません。
このためだけに Slack API を叩かないとだめなのかなーと思っていたんですが、なんとなく robot.adapter あたりを console.log() で見てたら見つかりました。

このあたりのドキュメントはどこかにまとまってるんですかね。。。


配列からランダムで1つ選ぶ、は msg.random(array) が使える

らしいです。Hubot の公式ドキュメントに書いてあります。
https://hubot.github.com/docs/scripting/#random

(Hubot のドキュメントは見づらい...!)


メンションは名前でもIDでも送れる

channels.info で取得した channel オブジェクトの members からはユーザーの ID しか取れないので、
てっきり最初は別途 users.info を叩いてユーザー名を取得し直さないといけないんだと思ってました。

が、Slack API のドキュメントをちゃんと読むと、メンションをとばすときは ID で OK ということが書かれています。

https://api.slack.com/docs/message-formatting#linking_to_channels_and_users

むしろ

The readable name can be included after the ID, by separating them with a pipe (|) character.

なので、ID の後ろに name をつけても良いよ、ぐらいでしょうか。
ただ、 ユーザー名だけでも一応メンションはとばせるみたいです。

メンションは @userId でなく <@userId> とする必要があるみたいですね。


TODO

毎回同じ返事だと寂しいので、メッセージにバリエーションを持たせたいです。
→対応しました。GitHub 参照 https://github.com/zaki-yama/hubot-es2015/issues/3


リファレンス