LoginSignup
7
5

More than 3 years have passed since last update.

Google Apps Scriptでシャッフルランチのグループ分けをしてくれるSlack Botをつくる

Last updated at Posted at 2019-06-20

いま働いてる職場では、シャッフルランチといって、定期的にメンバーをシャッフルしてランチをする機会があるのですが、人数が多いとグループ分けになかなか骨が折れるので自動化してみました。

こんなことができます

「○人ずつシャッフルして」とメンションを飛ばすと、そのチャネルにいるメンバー一覧を取得して、ランダムにグルーピングしてくれます。
スクリーンショット_2019-06-20_17_36_33.png
(モザイクだらけ・・ :joy:

以下、思い出しながら書いてるので情報が断片的です。 :bow:

実装手順

slack botをつくる

botの作り方はドキュメントに書いてるので見てください。 :pray:
まずappを作成してbotを作成、という流れです。
Enabling interactions with bots | Slack

ここで作成したbot userのトークンを利用します。(xoxb-からはじまるやつ)
スクリーンショット_2019-06-20_17_34_37.png

今回はconversations.members methodを使ってメンバー一覧を取得するので、appのOAuth & Permissionsでchannels:read, groups:readを追加します。

image.png

Google Apps ScriptでAPIをつくる

Apps Scriptから「新規スクリプト」をポチってください。

で、以下のコードをコピペ。

// 先ほど作成したbotのトークンをコピペ
var botToken = 'xoxb-THIS_IS_BOT_TOKEN';

function members_endpoint(channel, cursor) {
  return 'https://slack.com/api/conversations.members?token='+botToken+'&limit=1000&channel='+channel+'&cursor='+cursor;
}

function user_endpoint(user) {
  return 'https://slack.com/api/users.info?token='+botToken+'&user='+user;
}

function doPost(e) {
  var postData = JSON.parse(e.postData.getDataAsString());
  var res = {};

  if(postData.type == 'url_verification') {
    res = {'challenge':postData.challenge}
    return ContentService.createTextOutput(JSON.stringify(res)).setMimeType(ContentService.MimeType.JSON);
  }

  var channel = postData.event.channel;
  var user = postData.event.user;
  var text = postData.event.text;
  var ts = postData.event.ts;

  // なぜかslack apiで複数回叩かれてしまうバグ(仕様?)があるのでキャッシュを利用して複数回応答を阻止
  var cache = CacheService.getScriptCache();
  var cacheKey = channel + ':' + ts;
  var cached = cache.get(cacheKey);
  if (cached != null) {
    console.log('対応済みなのでなにもしないぞ!');
    return;
  }
  cache.put(cacheKey, true, 1800); // 30分キャッシュする

  // 「シャッフル」という単語意外には適当な言葉を返す
  if (text.indexOf('シャッフル') == -1 ) {
    replyToSlack(channel, user, getRandomText());
    return;
  }

  var members = shuffle(getMembers(channel));
  // ユーザの名前を取得
  var memberNames = getMemberNames(members);
  var chunk = getChunk(text);
  var texts = textsOfGroup(memberNames, chunk);
  var messages = [];
  messages.push(chunk+'人ずつシャッフル!!');
  messages.push('```');
  messages = messages.concat(texts);
  messages.push('```');
  replyToSlack(channel, user, messages.join("\n"));

  // なにも返さなくても200 statusが返却されるので放置
  res = {'ok': true};
  return ContentService.createTextOutput(JSON.stringify(res)).setMimeType(ContentService.MimeType.JSON);
}

function getChunk(text) {
  var result = text.match(/([0-90-9]+)人/);
  // chunkの指定がない場合は3人ずつにする
  if (!result) {
    return 3;
  }
  // 人数の入力が全角だった場合半角にする
  var number = result[1].replace(/[A-Za-z0-9]/g, function(s) {
    return String.fromCharCode(s.charCodeAt(0) - 65248);
  });
  return parseInt(number);
}

function getMemberNames(members) {
    // ユーザの名前を取得
  var memberNames = []
  for (var i = 0; i < members.length; i++ ) {
    var member = members[i];
    var response = UrlFetchApp.fetch(user_endpoint(member));
    var json = JSON.parse(response.getContentText());
    // bot, 削除ユーザは無視
    if (json.user.is_bot || json.user.deleted ) {
      continue;
    }
    memberNames.push(json.user.name);
  }
  return memberNames;
}

function shuffle(a) {
    var j, x, i;
    for (i = a.length - 1; i > 0; i--) {
        j = Math.floor(Math.random() * (i + 1));
        x = a[i];
        a[i] = a[j];
        a[j] = x;
    }
    return a;
}

function getRandomText(){
  var texts = [
    'ごめん',
    'せやかて',
    'まじか',
    'ありがとう',
    'たしかに',
    'ありっちゃあり',
    'そうなんや',
    'それな',
    'そっかぁ',
    'うーん',
    'どうしよかな',
    'ほんまに?',
    'うんうん',
    'へぇ〜',
    '一理あるな'
 ];

  return texts[Math.floor(Math.random() * texts.length)];
}

function textsOfGroup(array, chunk) {
  var textsOfGroup = [];
  var i,j;
  for (i = 0, j = array.length; i < j; i += chunk) {
    var group = array.slice(i, i+chunk);
    var alphabet = columnToLetter((textsOfGroup.length+1));
    var text = alphabet + '' + group.join('');
    if (group.length == 1) {
      text = text + ' ← 一人なのでどこかのグループにマージしてね';
    }
    textsOfGroup.push(text);
  }
  return textsOfGroup;
}

function getMembers(channel) {
  var cursor = '';
  var members = [];
  do {
    var response = UrlFetchApp.fetch(members_endpoint(channel, cursor));
    var json = JSON.parse(response.getContentText());
    for(var i = 0; i < json.members.length; i++ ) {
      var member = json.members[i];
      members.push(member);
    }
    cursor = json.response_metadata.next_cursor;
  } while (cursor != '');

  return members;
}

function replyToSlack(channel, user, message){
  var url = 'https://slack.com/api/chat.postMessage'

  var data = {
    'channel' : channel,
    'text' : '<@'+user+'> '+message,
    'as_user' : true
  };

  var options = {
    'method' : 'post',
    'contentType' : 'application/json; charset=UTF-8',
    'headers' : {'Authorization': 'Bearer '+botToken},
    'payload' : JSON.stringify(data)
  };

  var response = UrlFetchApp.fetch(url, options)
  console.log(response.getContentText());
}

// 1 -> A のようにアルファベットにする
function columnToLetter(column) {
  var temp, letter = '';
  while (column > 0)
  {
    temp = (column - 1) % 26;
    letter = String.fromCharCode(temp + 65) + letter;
    column = (column - temp - 1) / 26;
  }
  return letter;
}

細かい説明は省きますが、だいたい以下のようなことをしてます。

  • リクエストが飛んできたチャンネルの情報を取得してメンバー一覧を取得
  • そのメンバーを適当にシャッフルして整形して返答する
  • 「シャッフル」以外の単語で話しかけられたら適当に返答する

webアプリケーションとして公開

さきほどのスクリプトをWebアプリケーションとして公開します。
公開 > ウェブアプリケーションとして導入 をクリックして公開します。
ここで発行されたURLをコピーしておきます。

botがメンションに反応できるようにする

botもAPIもできたので、あとは連携させるだけですね。

botがメンションに反応できるように、Event Subscriptionの設定をします。
Request URLに先程発行されたAPIのURLを入力し、 app_mentionをsubscribeします。
スクリーンショット_2019-06-20_17_34_04.png

スクリーンショット 2019-06-20 17.34.13.png

以上です。

あとは、botをチャネルに招待して、メンションを飛ばせばOK。 :ok_hand:

botの権限が足りない等、漏れがあるかもしれません。良ければコメントでご指摘ください。

ではでは :wave:

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5