2020/08/06 追記
fondeskのSlack通知に対して営業電話はメンションさせないようにした のカスタマイズを追加して運用しています。
2020/12/01 追記
fondeskの仕様変更に伴い、Google Apps Scriptの中身を変更しています。最新は以下のコードをご確認ください。https://gist.github.com/YoshiteruIwasaki/420aa7c9473a3adf35d9f209b24e4ee9
変更箇所はこちらを参考にしてください。
https://gist.github.com/YoshiteruIwasaki/420aa7c9473a3adf35d9f209b24e4ee9/revisions#diff-83d992e9b6d130685d1f90d81bec58c0213629999394b0429794b4bf9de8d4c8
検証時のPOSTデータはこんな感じになります。
curl -X POST -H 'Content-type: application/json' --data '{"text":"*発信者* 株式会社テスト ヤマダ\n*あて先* 佐藤\nメールご確認くださいとのことでした。"}' https://hooks.slack.com/services/XXXXXXXXX
2020/12/02 追記
さらに改修しました。
どうやらblocksの中にテキストが入ってくるのでそれをパースする必要があるようです。検証時のPOSTデータもこんな素直なパラメータではないようです(涙
https://gist.github.com/YoshiteruIwasaki/420aa7c9473a3adf35d9f209b24e4ee9/revisions#diff-83d992e9b6d130685d1f90d81bec58c0213629999394b0429794b4bf9de8d4c8
fondesk とは
オフィスや事務所の電話を代行して、チャットやメールでお知らせするサービスです。
自動音声とかじゃなく、普通に人間が出て応対してくれます。
しくみとしては、会社などにかかってきた電話をfondesk側の電話番号に転送してオペレーターが対応してくれます。
Twilioとか裏では使っているんでしょうか。
受けた電話の内容については各種ツールで通知を受け取ることが可能です。
導入にあたって用意が必要だったこと
fondeskのお申し込みにはクレジットカードと電話番号が必要になります。
NTTの転送設定
かかってきた電話をfondeskの番号に転送するため、電話回線の転送サービスが必要になります。
電話回線はNTTを使っているのですが、NTTの転送サービス「ボイスワープ」の申し込みが必要でした。
ひかり電話のボイスワープは初期の工事費 2,000円、月額利用料 500円でした。
NTTに工事を申し込んで半日後くらいには使えるようになりました。
Slack通知の設定
通知先にはメール以外にチャットツールを設定できます。Slackの場合は指定したチャンネルへ通知が来ます。
しかし、残念ながら現状ではメンション機能はありません。その理由についてはこちらのヘルプ「 電話の宛先によって通知先を変えたり、メンションを付けたりすることはできますか? 」に記載があります。
宛先に応じた通知機能は多くのご要望をいただいていますが、期待される精度での通知が難しいだけでなく、ユーザーのみなさまにも多くのご負担がかかってしまうため、慎重に検討を進めています。
わかる。
ただ、しかしながらメンションはやはりほしいところ。
ということで自前で実装しました。
最初に考えた実装方針
fondeskはメール通知ができるので、以下のような方針を最初考えました。
- fondeskからメール通知を行う
- AWS SESでメールを受信する
- Lambdaでメールの本文を解析する
- LambdaからSlackのチャンネルへメンションをつけてPOSTする
もともとメール通知だけしか無いシステムで、Slackにも通知をさせたいよね、ということで対応した経験がある方法です。
この方法のデメリット
ただこの方法にはいくつかデメリットがあります。
- メールアカウントを用意しないといけない
- AWSの環境を用意しないといけない
- 名前とSlackのユーザーIDのマッピングの保守がしづらい(プログラム内にMapとして列挙する、あるいは何らかのDBを使う必要がある)
この方法はもともとメール通知をしている、Slack通知がないという状況下で採用した方法なので、最初からSlack通知に対応している環境下であれば、もっと楽な方法がありそうなものです。
fondeskでslackにメンションを追加するようなカスタマイズされている方いらっしゃらないかなー
— ニートン 岩崎 (@neeton_iwasaki) November 8, 2019
ということでもっと簡単な方法はないかと思っていたところ、中の方からヒントをいただき次のように実装することにしました。
実装方針
- fondeskからSlack通知を行う
- Google Apps ScriptでSlackへの投稿を検知
- Google Apps Scriptで本文を解析する
- Google Apps ScriptからSlackのチャンネルへメンションをつけてPOSTする
この方法のメリット
- メールアカウント不要
- AWSなどのサーバー環境不要
- 名前とSlackのユーザーIDのマッピングをGoogle Spreadsheetで行えるためエンジニア以外でも追加などが行いやすい
ということで、Google Apps Scriptを使ってメンションを追加するBOTを作りました。
Google Apps ScriptによるBOT用アプリの作り方
こちらについては多くの有用な記事がありますので、それらを参考にしていただくといいかと思います。
僕が参考にしたのはこのあたりです。
GASからメッセージを投げつける方法
メッセージを受信する方法
GASをSlackのwebhookのエンドポイントにする方法
GASのスクリプト更新時の反映手順
設定するアプリとしてはまあよくある感じで
- Incoming Webhooks を有効にする
- Scopes を設定する
- Event Subscriptions を有効にする
あたりの設定を行う感じです。
このBOTのおすすめポイント
- 漢字とカタカナに対応
- 同姓や同じ部署のときに該当する複数人にメンションが送られる
- 該当者がいないときには@hereにメンションが送られる
というメリットがあります。発信者側の名前は検索対象でないので、S社の孫さんから前澤さんへ連絡が来たときに、社内の孫さんにはメンションは飛びません。よかった!
コードの中身
さて実際のGoogle Apps Scriptの中身です。
function doPost(e) {
var channel = 'XXXXXXX'; // fondeskの通知先チャンネル
var url = 'https://hooks.slack.com/services/XXXXXXX'; // Incoming Webhook URL
var sheetName = 'list'; //名簿リストのシート名
var sheetRange = 'A2:C100'; //名簿リストのスプレッドシートデータ取得範囲
// デコードしたPOSTデータを文字列として取得
var decodeData = unescapeUnicode(e.postData.getDataAsString());
// JSONをJavaScriptのオブジェクトに変換
var postData = JSON.parse(decodeData);
var res = {};
// 認証
if (postData.type == 'url_verification') {
res = {
'challenge': postData.challenge
};
return ContentService.createTextOutput(JSON.stringify(res)).setMimeType(ContentService.MimeType.JSON);
}
var getText = "";
if (postData.type == "event_callback" && postData.event.type == "message" && postData.event.channel == channel) {
//投稿内容の取得
var thread = postData.event.event_ts; // fondeskの投稿に対して返信する形式にしたいのでスレッドのIDを取得
var attachments = postData.event.attachments;
//投稿内容の中で「内容」欄の箇所のみを抽出
//ここで「発信者」による連絡先の振り分けといった対応もできそうです
for (var i = 0; i < attachments.length; i++) {
var fields = attachments[i].fields;
for (var j = 0; j < fields.length; j++) {
if (fields[j].title == "内容") {
getText = getText + fields[j].value;
}
}
}
//該当するユーザーの検索
var recordsheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
var range = recordsheet.getRange(sheetRange);
var data = range.getValues();
var message = '';
var nobody = true;
var array = [];
// 姓名とSlackIDのマップを検索
for (var k = 0; k < data.length; k++) {
if (data[k][0] != null && data[k][0] != "") {
var result1 = getText.indexOf(data[k][0]);
var result2 = getText.indexOf(data[k][1]);
if (result1 !== -1 || result2 !== -1) {
if (array.indexOf(data[k][2]) == -1){
// メンションを追加
message = "<@" + data[k][2] + "> " + message;
nobody = false;
// 同じユーザーに何個もメンションをつけないように回避
array.push(data[k][2]);
}
}
}
}
if (nobody) {
//該当者がいない場合
message = "<!here> 宛先不明のお電話です。 対応したらこの投稿にスタンプをお願いします。" + message;
} else {
message = message + " お電話がありました。対応したらこの投稿にスタンプをお願いします。";
}
//返信投稿
var options = {
"method": "post",
"contentType": "application/json",
"payload": JSON.stringify({
"text": message,
"thread_ts": thread,
"reply_broadcast": true
})
};
UrlFetchApp.fetch(url, options);
}
}
function unescapeUnicode(string) {
return string.replace(/\\u([a-fA-F0-9]{4})/g, function(matchedString, group1) {
return String.fromCharCode(parseInt(group1, 16));
});
};
使い方
Spreadsheetにこんな感じでデータをリストアップします。
オペレーターはだいたい名字の漢字あるいはカタカナで「〇〇様宛」と伝えてくれるので、検索に引っかけやすいようにこのようなリストを作ります。
なまえ辞書を使うことで漢字の精度を高めることができます。なまえ辞書の使い方
なんとなく名前辞書に登録してある氏名は漢字で、そうでない場合にはカタカナを使ってオペレーターの方が内容を送ってくれる感じがあります。
例えば「採用」あるいは「人事」で連絡が来たら田中さんにメンションをつけたい、という場合には
セイ | 姓 | id |
---|---|---|
タナカ | 田中 | U000000002 |
サイヨウ | 採用 | U000000002 |
ジンジ | 人事 | U000000002 |
のような感じでデータを入れます。
「人事部の田中様」みたいな場合にもidが一緒の場合にはメンションは1つにまとめられます。
「総務」で連絡が来たら上田さん、遠藤さん、西川さんにメンションをつけたい、という場合には
セイ | 姓 | id |
---|---|---|
ソウム | 総務 | U000000003 |
ソウム | 総務 | U000000004 |
ソウム | 総務 | U000000005 |
のような感じでデータを入れます。そうすることで担当者全員にメンションが行くようになります。
同姓の場合も同じように複数人にメンションが行くようになります。これはぶっちゃけ、社内の人間が電話応対しても「弊社には山田は複数人おりますが、どちらの山田宛でしょうか。。。」みたいな感じで判別するの大変なので、このような実装で十分なのではないかなと思っております。
ちなみにいまのところ個人名以外では以下のような感じで入れています。(人事・採用系の電話が多いことがわかります。)
- ネットワーク
- 採用
- 人事
- 派遣
- 求人
- 人材
- 教育
- 研修
開発する上での注意点
Incoming Webhook と Outgoing Webhook の組み合わせでは通知をキャッチできない
Outgoing Webhookには メッセージの添付ファイルの内容が送信POSTデータに含まれないため、fondeskのアプリから送信される形式のデータは取得することができません。
Events APIの使えるアプリを作る必要があります。
Attachmentの中身はエンコードされている
textであればそのまま使えるのですが、Attachmentの中身はエンコードされて送られてくるため、デコードする必要があります。
function unescapeUnicode(string) {
return string.replace(/\\u([a-fA-F0-9]{4})/g, function(matchedString, group1) {
return String.fromCharCode(parseInt(group1, 16));
});
};
を参考にさせていただきました。
メンションの書き方
にあるように指定された記法で書かないとメンションになりません。また、usernameでのメンションは使えなくなったため、user_idを調べてメンションする必要があります。
デバッグ方法
Loggerでのデバッグがうまく動かなかったのですが、Spreadsheetに出力するようにしてしまうのが手っ取り早いです。
// debug
var ss = SpreadsheetApp.getActiveSpreadsheet(); //スプレッドシートを取得
var sheet = ss.getSheetByName("hoge"); //シート名でシートを取得
sheet.getRange(1, 1).setValue(postData); //シートのA1セルにメッセージのテキストを取得
デバッグ用のPOSTデータ例
デバッグするために毎回電話をするのは非現実的です。fondeskのアプリからはだいたいこんなようなデータがPOSTされてきていますので、適当なIncoming Webhookを作っておいてPOSTさせるのが良いと思います。
PAYLOAD=`cat << EOS
payload={
"channel": "#fondesk",
"username": "fondesk post tester",
"attachments":[
{
"color":"good",
"fields":[
{
"title":"発信者",
"value":"株式会社テスト ヤマダ",
"short":false
},
{
"title":"折り返し先の電話番号",
"value":"00-0000-0000",
"short":true
},
{
"title":"折り返しの連絡",
"value":"必要",
"short":true
},
{
"title":"内容",
"value":"XXXXX様宛。メールご確認くださいとのことでした。",
"short":false
}
]
}
],
"icon_emoji": ":iphone:"
}
EOS`
curl -X POST --data-urlencode "$PAYLOAD" https://hooks.slack.com/services/XXXXXXXXX
まとめ
fondeskはもともと素晴らしいサービスですが、メンションがつくようになったことで通知がだいぶスッキリしました!
「メンションだけ見てもらえれば大丈夫です!」
と自信を持って言えるようになりました。