いま働いてる職場では、シャッフルランチといって、定期的にメンバーをシャッフルしてランチをする機会があるのですが、人数が多いとグループ分けになかなか骨が折れるので自動化してみました。
こんなことができます
「○人ずつシャッフルして」とメンションを飛ばすと、そのチャネルにいるメンバー一覧を取得して、ランダムにグルーピングしてくれます。
(モザイクだらけ・・ )
以下、思い出しながら書いてるので情報が断片的です。
実装手順
slack botをつくる
botの作り方はドキュメントに書いてるので見てください。
まずappを作成してbotを作成、という流れです。
Enabling interactions with bots | Slack
ここで作成したbot userのトークンを利用します。(xoxb-
からはじまるやつ)
今回はconversations.members methodを使ってメンバー一覧を取得するので、appのOAuth & Permissionsでchannels:read
, groups:read
を追加します。
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します。
以上です。
あとは、botをチャネルに招待して、メンションを飛ばせばOK。
botの権限が足りない等、漏れがあるかもしれません。良ければコメントでご指摘ください。
ではでは