Help us understand the problem. What is going on with this article?

fondeskのSlack通知に対してメンションを追加するBOTを作った

fondesk とは

オフィスや事務所の電話を代行して、チャットやメールでお知らせするサービスです。

スクリーンショット 2019-11-15 0.01.21.png

https://www.fondesk.jp/

自動音声とかじゃなく、普通に人間が出て応対してくれます。
しくみとしては、会社などにかかってきた電話をfondesk側の電話番号に転送してオペレーターが対応してくれます。
Twilioとか裏では使っているんでしょうか。
受けた電話の内容については各種ツールで通知を受け取ることが可能です。

スクリーンショット 2019-11-15 0.00.00.png

導入にあたって用意が必要だったこと

fondeskのお申し込みにはクレジットカードと電話番号が必要になります。

NTTの転送設定

かかってきた電話をfondeskの番号に転送するため、電話回線の転送サービスが必要になります。
電話回線はNTTを使っているのですが、NTTの転送サービス「ボイスワープ」の申し込みが必要でした。

転送設定について
ひかり電話ボイスワープ

ひかり電話のボイスワープは初期の工事費 2,000円、月額利用料 500円でした。
NTTに工事を申し込んで半日後くらいには使えるようになりました。

Slack通知の設定

通知先にはメール以外にチャットツールを設定できます。Slackの場合は指定したチャンネルへ通知が来ます。

スクリーンショット 2019-11-15 22.42.05.png

しかし、残念ながら現状ではメンション機能はありません。その理由についてはこちらのヘルプ「 電話の宛先によって通知先を変えたり、メンションを付けたりすることはできますか? 」に記載があります。

宛先に応じた通知機能は多くのご要望をいただいていますが、期待される精度での通知が難しいだけでなく、ユーザーのみなさまにも多くのご負担がかかってしまうため、慎重に検討を進めています。

わかる。

ただ、しかしながらメンションはやはりほしいところ。

ということで自前で実装しました。

最初に考えた実装方針

fondeskはメール通知ができるので、以下のような方針を最初考えました。

  1. fondeskからメール通知を行う
  2. AWS SESでメールを受信する
  3. Lambdaでメールの本文を解析する
  4. LambdaからSlackのチャンネルへメンションをつけてPOSTする

もともとメール通知だけしか無いシステムで、Slackにも通知をさせたいよね、ということで対応した経験がある方法です。

この方法のデメリット

ただこの方法にはいくつかデメリットがあります。

  • メールアカウントを用意しないといけない
  • AWSの環境を用意しないといけない
  • 名前とSlackのユーザーIDのマッピングの保守がしづらい(プログラム内にMapとして列挙する、あるいは何らかのDBを使う必要がある)

この方法はもともとメール通知をしている、Slack通知がないという状況下で採用した方法なので、最初からSlack通知に対応している環境下であれば、もっと楽な方法がありそうなものです。

ということでもっと簡単な方法はないかと思っていたところ、中の方からヒントをいただき次のように実装することにしました。

実装方針

  1. fondeskからSlack通知を行う
  2. Google Apps ScriptでSlackへの投稿を検知
  3. Google Apps Scriptで本文を解析する
  4. Google Apps ScriptからSlackのチャンネルへメンションをつけてPOSTする

この方法のメリット

  • メールアカウント不要
  • AWSなどのサーバー環境不要
  • 名前とSlackのユーザーIDのマッピングをGoogle Spreadsheetで行えるためエンジニア以外でも追加などが行いやすい

ということで、Google Apps Scriptを使ってメンションを追加するBOTを作りました。

bot.jpeg

Google Apps ScriptによるBOT用アプリの作り方

こちらについては多くの有用な記事がありますので、それらを参考にしていただくといいかと思います。

僕が参考にしたのはこのあたりです。

GASからメッセージを投げつける方法
メッセージを受信する方法
GASをSlackのwebhookのエンドポイントにする方法
GASのスクリプト更新時の反映手順

設定するアプリとしてはまあよくある感じで

  • Incoming Webhooks を有効にする
  • Scopes を設定する
  • Event Subscriptions を有効にする

あたりの設定を行う感じです。

このBOTのおすすめポイント

  1. 漢字とカタカナに対応
  2. 同姓や同じ部署のときに該当する複数人にメンションが送られる
  3. 該当者がいないときには@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));
  });
};

https://gist.github.com/YoshiteruIwasaki/420aa7c9473a3adf35d9f209b24e4ee9

使い方

Spreadsheetにこんな感じでデータをリストアップします。

オペレーターはだいたい名字の漢字あるいはカタカナで「〇〇様宛」と伝えてくれるので、検索に引っかけやすいようにこのようなリストを作ります。

スクリーンショット 2019-11-16 0.37.48.png

なまえ辞書を使うことで漢字の精度を高めることができます。なまえ辞書の使い方

なんとなく名前辞書に登録してある氏名は漢字で、そうでない場合にはカタカナを使ってオペレーターの方が内容を送ってくれる感じがあります。

例えば「採用」あるいは「人事」で連絡が来たら田中さんにメンションをつけたい、という場合には

セイ 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));
  });
};

http://perutago.seesaa.net/article/202801583.html

を参考にさせていただきました。

メンションの書き方

https://qiita.com/ryo-yamaoka/items/7677ee4486cf395ce9bc#%E8%A8%98%E6%B3%95

にあるように指定された記法で書かないとメンションになりません。また、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

まとめ

002.png

fondeskはもともと素晴らしいサービスですが、メンションがつくようになったことで通知がだいぶスッキリしました!

「メンションだけ見てもらえれば大丈夫です!」

と自信を持って言えるようになりました。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした