serverless でチャットを確実に受取りたい - 家庭を支える技術 Advent Calendar 2016 2日目 #家庭を支える技術

このエントリーは、家庭を支える技術 Advent Calendar 2016 - Adventar の2日目の記事として書きました。

昨年までとは、趣きを変えて、家庭内における連絡手段の冗長化についてです。

家庭内チャットについて

我々のチームでは、チャットサービスとして以下のサービスを利用しています。

  • slack
  • LINE
  • chatwork

使い分けとしては、帰宅の連絡など普段のコミニケーションとしてはLINE、Calendarやtodoの共有、事案メールの通知などにはslackという感じになっています。
chatworkは、slack導入までは、メインに使っていましたが、今は、家庭内の連絡に使うことはほぼなくなっています。ただし妻が仕事で利用しているので、妻としては見やすいということで、アカウントはそのままになっています。

気づかない問題

どれか一つの方法でだけ連絡した場合、気づかないときには、全然気づかず、レスポンスがすぐ欲しいときに困ることがありました。
もちろん、緊急のときは、電話を鳴らしたりもしますが、それも気づかないということがあったりもしました。*1

マルチポストしたい

複数の経路をもっているので、マルチポストできれば、どれかには、気づいてもらえるかもしれません。
そこで、どこか一つに書くとそれをよしなに他のチャットにも書き込んでくれる仕組みを作りました。

実装のはなし

今回は、slackの特定のchannelに書き込むと、Outgoing Webhookによって、その内容を GCPの Cloud Functionsの http trigger に送り、Cloud Functionsが LINE Notifyと chatworkの APIをキックして、内容を書き込んでくれる仕組みを整えました。

/**
 * HTTP Cloud Function.
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */

var request = require('request');
var config = require('config');
var lineConfigAccessToken = config.Line.access_token;

exports.sendLineNotify = function sendLineNotify (req, res) {
  var options = {
    url: 'https://notify-api.line.me/api/notify',
    method: 'POST',
    headers: {
      'Content-Type':'application/x-www-form-urlencoded',
      'Authorization': 'Bearer ' + lineConfigAccessToken,
    },
    form: { message : `${req.body.text}` }
  };
  // request LINE Notify
  request(options, recallback);

  function recallback(error, response, body) {
    if (error == true) {
      console.log(body);
    }
  }

  res.send('ok');
};
/**
 * HTTP Cloud Function.
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */

var request = require('request');
var config = require('config');
var chatworkConfigRoomId = config.Chatwork.room_id;
var chatworkConfigApiToken = config.Chatwork.api_token;

exports.sendChatwork = function sendChatwork (req, res) {
  var options = {
    url: 'https://api.chatwork.com/v1/rooms/' + chatworkConfigRoomId + '/messages',
    method: 'POST',
    headers: {
      'Content-Type':'application/x-www-form-urlencoded',
      'X-ChatWorkToken': chatworkConfigApiToken,
    },
    form: { body : `from_slack [${req.body.user_name}]: ${req.body.text}` }
  };
  // request Chatwork API
  request(options, callback);

  function callback(error, response, body) {
    if (error == true) {
      console.log(body);
    }
  }

  res.send('ok');
};

その他packegeなどをふくめたソースは↓

Outgoing Webhookのリクエストを受取り、LINE Notifyとchatwork API のエンドポイントにPOST requestを送るだけで完了です。
APIをcallした時のエラーハンドリングなどをほぼしていないので、異常系の処理ができていないですが、正常系は問題なく稼働しています。

プロトタイプ

slackにherokuで動かしているhubotがいたので、はじめは、hubotのscriptを実装して、意図通り動くところまでのプロトタイプを作りました。

# Description:
#   Emergency bot
#
# Commands:
#   emergency channel hear
#
# Author:
#   tknzk

module.exports = (robot) ->

  robot.hear /.*/, (res) ->
    room = res.envelope.room
    text = res.message.text
    console.log room
    console.log text

    if room == 'emergency'

      # line
      request = res.http("https://notify-api.line.me/api/notify")
        .headers("Authorization": "Bearer " + process.env.HUBOT_LINE_NOTIFY_API_TOKEN)
        .headers("Content-Type": 'application/x-www-form-urlencoded')
        .post("message=" + text)
      request (err, resp, body) ->
        if err
          res.send err
        else
          if body.length == 0
            res.send "Failed "
          else
            res.send body

      # chatwork
      request = res.http("https://api.chatwork.com/v1/rooms/" + process.env.HUBOT_CHATWORK_ROOM_ID + "/messages")
        .headers("X-ChatWorkToken": process.env.HUBOT_CHATWORK_API_TOKEN)
        .headers("Content-Type": 'application/x-www-form-urlencoded')
        .post("body=" + text)
      request (err, resp, body) ->
        if err
          res.send err
        else
          if body.length == 0
            res.send "Failed "
          else
            res.send body

ただ、それではあんまりにも技術的な進歩がなさすぎたのと、昨今の流行りからお手軽に試すにはserverlessでしょうということでCloud Functionsで実装してみました。
AWS lambda を選ばなかったのは、趣味の問題です。*2 *3

さて、動作的には、↓のようなかんじになります。slackの特定のchannelに書き込むと同じものがLINEとchatworkに転送されます。

f:id:tknzk:20161201215219g:plain

まとめ

チャットを確実に受け取りたいということで、Cloud Functionsでマルチポストを実装してみました。
Advent Calendar Drivenで開発したので、実は運用はまだ行っていないのが実情です。。
なので、今日から運用開始です。

www.adventar.org

*1:妻がnotificationを切りがちという可能性もあります

*2:GCPに寄せていきたいと思っている

*3:AWS lambda + API GATEWAYは、機能はそれなりにそろってるが、その分設定も細かいのでめんどくさいっていうこともあったりもする。