Slackのslash command + Googleスプレッドシート + GASで貸出帳を作った
はじめまして小森です。仕事は総務系メインで、たまに情シス・人事労務やってます。
社内コミュニケーションにSlackを使っています。私自身は去年入社してから初めて使っていますが、メールに比べて圧倒的に便利ですね。Skypeも使っていたことはありますが、やっぱ個対個中心でした。Slackはチャットはもちろん頻繁に活用してますが、通知系も便利に利用しています。チームのタスク管理にTrelloを利用しているので、新規カードの追加時とかにSlackに通知がくるようにしています。とても便利。IFTTTを使って採用の通知メールを受信したら通知されるようにもしてます。
今回はSlackのslash commandを利用した貸出表を結構ちゃんと作ったのでnote化してみることにしました。Slackは使ってても、エンジニアじゃないとあんまりslash commandは使ってないんじゃないかなと思ってます。どうなんだろ。
/remind @taro 小森さんに連絡する at6pm
/calc 200*3
/unipos @taro +39 いつもありがとう!
私はこの3つしか使ってない気がする。今日も元気に働きバチ!でおなじみremindはとても便利。calcは標準じゃ入ってなかったような。結構便利。uniposは今流行りのサンクスポイントを送り合うツールです。気になる方は下記参照。
今回実現したかったこと
mobileWifiの貸出管理をおこないたかったです。そんなに台数があるわけじゃないんですが、やっぱり人数が増えてくると「この前貸したでしょ?」「いえ、私持ってませんけど」みたいなことがよく起こります。ExcelやGoogleスプレッドシートだけで管理するのが手軽ですが、記入漏れとかが頻発することが予想できます。Slackなら普段から利用しているので、多少は解消になるのではと思っています。
オリジナルのslash command基礎
貸出管理を実現する前に、slash commandの基礎を書いておきます。Slack単体ですとオリジナルのslash commandは作れません。https通信ができる環境が必要とのことです。色々選択肢はあるのですが、無料かつ程よいのはGoogleスプレッドシートとGAS(Google Apps Script)の組み合わせだと思います。今回も利用しているのですが、スプレッドシートは簡易なデータベースになるのでとても使い勝手が良いです。
ではまず下準備。Googleスプレッドシートを新規作成します。
ファイル名はなんでも大丈夫です。今回は「inout」「list」の2つのシートを使いますが詳細は後ほど。
ツール→スクリプトエディタからGASエディタを開きましょう。
エディタを開くとmyFunctionなるものが自動作成されますが、使わないので削除します。削除したら、下記をコピペします。この基礎ではslash commandを実行すると「テスト」と表示するサンプルを作ります。
function doPost(e) {
var verificationToken = e.parameter.token;
if (verificationToken != '********************') { // AppのVerification Tokenを入れる
throw new Error('Invalid token');
}
var result ='テスト';
var response = {text: result};
return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(ContentService.MimeType.JSON);
}
ここまで作ってセーブしたら一旦この画面から離れて、下記Slack APIのサイトを開きます。
この画面に遷移します。遷移できない場合、もしかしたらAdmin権限が必要かもしれませんので管理者に聞いてもらうか、お試しなのでSlackの新しいWorkspaceを作った方がいいかもしれません。
右上の「Create New App」をクリックします。
App Nameは適当で大丈夫です。このAppに複数のslash commandがひも付きます。必要事項を入力したら右下の「Create App」をクリックします。
まず「Basic Information」を選択して、少し下の方にスクロールすると「Verification Token」という項目に長い文字列があります。こちらをコピペしてGASの「*************************」となっていた部分と置き換えましょう。
置き換えたらまだGASの画面で作業します。
公開→ウェブアプリケーションとして導入、をクリックします。
これから何度かこの更新作業を行うことになりますが、必ず「新規作成」を選択して下さい。新規にしないと最新のコードを認識してくれないようです。
保存が完了したら「現在のウェブアプリケーションのURL」を全てコピーして下さい。
Slack APIに戻ります。Slash Commandsに移って「Create New Command」をクリックして下さい。
「Command」は実際のslash commandを入力します。「Request URL」は先程GAS画面で最後に取得したURLを貼り付けます。「Save」をクリックします。
後もう少しです。「Basic Information」に移って「Install App to Workspace」をクリックします。ここまでうまくいくと。。。
テスト、と表示するだけですがslash commandが完成しました!少しだけソースの説明です。
function doPost(e) {
}
slash commandを入力するとGASに通信が行きますが、doPostというmain関数が実行されます。これはこの名前じゃないと駄目です。
var verificationToken = e.parameter.token;
if (verificationToken != '********************') { // AppのVerification Tokenを入れる
throw new Error('Invalid token');
}
ここはコピペして持ってきたので詳しくわかりません。不正アクセスされていないか検証してるんじゃないかと思います。
var result ='テスト';
var response = {text: result};
return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(ContentService.MimeType.JSON);
一律「テスト」という文字列を表示させるのでresultに固定値を入れています。Slackへは気味の悪い拡張子でおなじみのJSON形式で引き渡されます。ここらへんはとりあえず定型文として利用できるかと思います。
貸出帳の設計
なんとかここまで作りましたが、テストって表示するだけで終わっちゃつまらないので次行きます。今回作り始める前に、予め下記を考えてから作りました。
slash commandで全端末の状況を取得する
slash commandで貸出/返却入力が出来る
最低限のチェックのみで、細かいことは考えない
ここまで考えた後に、データの持ち方とslash commandの引数を同時に考えていきました。
今回は「inout」「list」の2つのシートを使いデータ管理をしています。「inout」シートはslash commandから「貸出」「返却」が行われると一番下の行に追記されていきます。「list」シートは「inout」シートの情報を集計しています。A列以外は標準の関数で構成しています。若干関数が複雑になっていますが、やっていることは「inout」で一番下から各Noを検索して、検索に引っかかった行を「list」に反映しています。lookup関数の下から検索するバージョンがあれば楽だったのですが、無いのでこのような形になっています。
1行目の関数を参考まで
A2:固定値
B2:=if(F2="in","貸出可","貸出中")
C2:=INDEX(inout!$B:$B,MAX(INDEX((inout!$A:$A=A2)*ROW(inout!$A:$A),)))
D2:=if(F2="in","",G2)
E2:=if(F2="in",G2,"")
F2:=INDEX(inout!$C:$C,MAX(INDEX((inout!$A:$A=A2)*ROW(inout!$A:$A),)))
G2:=INDEX(inout!$D:$D,MAX(INDEX((inout!$A:$A=A2)*ROW(inout!$A:$A),)))
データ構成を考えると同時に、slash commandの引数も考えていきました。
/wifi list
/wifi 端末ID 名前 in | out
別々のslash commandにしても良かったのですが、なんとなく1つで統一しました。
GASを書いてみる
function doPost(e) {
var verificationToken = e.parameter.token;
if (verificationToken != '***********************') { // AppのVerification Tokenを入れる
throw new Error('Invalid token');
}
var command = e.parameter.text.split(' ');
var result ='';
var listStartRow = 1;
var listStartColumn = 1;
var listEndRow = 6;
var listEndColumn = 5;
var wifiList = getListRange(listStartRow, listStartColumn, listEndRow, listEndColumn).getValues();
if(command[0] == 'list') {
result = getList(listStartRow, listStartColumn, listEndRow, listEndColumn);
} else if(isEnableWifiName(command, 0, 0, wifiList, listEndRow) && isInOrOut(command, 2)) {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('inout');
var lastRow = spreadsheet.getLastRow() + 1;
var today = new Date();
spreadsheet.getRange(lastRow, 1).setValue(command[0]);
spreadsheet.getRange(lastRow, 2).setValue(command[1]);
spreadsheet.getRange(lastRow, 3).setValue(command[2]);
spreadsheet.getRange(lastRow, 4).setValue(today);
result = "受け付けました。";
} else {
result = 'usage:\n/wifi list\nwifi一覧を表示します。\n\n/wifi wifi端末ID 名前 in|out\nout(貸出)in(返却)を登録します。';
}
var response = {text: result};
return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(ContentService.MimeType.JSON);
}
function isInOrOut(command, column) {
var result = false;
if(command[column] == 'in' || command[column] == 'out'){
result = true;
}
return result;
}
function isEnableWifiName(command, commandColumn, listColumn, wifiList, listEndRow) {
var result = false;
for(var i=0; i<listEndRow; i++){
if(command[commandColumn] == wifiList[i][listColumn]){
result = true;
}
}
return result;
}
function getList(listStartRow, listStartColumn, listEndRow, listEndColumn){
var result = '';
var range;
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('list');
range = spreadsheet.getRange(listStartRow, listStartColumn, listEndRow, listEndColumn);
for(var i=0; i<listEndRow; i++){
for(var j=0; j<listEndColumn; j++){
result = result + range.getValues()[i][j] + ' | ';
}
result = result + '\n';
}
return result;
}
function getListRange(listStartRow, listStartColumn, listEndRow, listEndColumn){
var result;
var range;
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('list');
range = spreadsheet.getRange(listStartRow, listStartColumn, listEndRow, listEndColumn);
return range;
}
全部を解説すると終わらなそうなので、大切なところとハマったところを中心に記載します。コードの中身を省いて、各関数が何をしているのか書きます。
function doPost(e) {
//メイン
}
function isInOrOut(command, column) {
//inかout以外の文字列だったらエラーにする関数
}
function isEnableWifiName(command, commandColumn, listColumn, wifiList, listEndRow) {
//端末IDが「list」シートに存在しない時にエラーにする関数
}
function getList(listStartRow, listStartColumn, listEndRow, listEndColumn){
//端末リストを表示する関数
}
function getListRange(listStartRow, listStartColumn, listEndRow, listEndColumn){
//「list」シートの使う部分を取得する関数。
}
あらためて見ると結構とっちらかってるので、もっと効率化できそうな気がしてきましたが、とりあえず動いているのでそのままにしておきますw
var command = e.parameter.text.split(' ');
slash commandから取得した引数(パラメタ)です。スペース区切りでcommandという配列に格納しています。
result = 'usage:\n/wifi list\nwifi一覧を表示します。\n\n/wifi wifi端末ID 名前 in|out\nout(貸出)in(返却)を登録します。';
改行コードにハマったのでピックアップ。\nで改行です。なんか最初コピペした時うまく改行されなかったのですが、理由不明。
for(var i=0; i<listEndRow; i++){
for(var j=0; j<listEndColumn; j++){
result = result + range.getValues()[i][j] + ' | ';
}
result = result + '\n';
}
for文とか2次元配列とか久しぶりに書いたので、どっちがどっちだかよくわからなくなりました。
var listEndRow = 6;
この数字重要でした。端末数に応じて修正して下さい。やり方次第ではこの値も動的に取得できると思うので、余力があったら試してみて下さい。
実行結果
基礎編と同じく、こちらを保存すると(新規作成での保存をお忘れなく!)slash commandが使えるようになります。
良い感じです!
おわり
いかがだったでしょうか?まずはコピペしてみて動かしてもらって、動きがわかったらカスタマイズしてみるといいと思います!