48
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

updated at

Organization

SNS + Lambda + Twilio で音声電話をかける v2

アラート発生時に AWS Lambda を使って音声電話をかける では単に電話をかけるだけでしたが、

  • 複数人に同時に電話をかけたい
    • 1人だと、その人が電話に出なかったら終了なので
  • 夜から朝方までの時間帯で電話を鳴らしたい
    • 日中はアラートメールで十分

という変更をしたくなってきたので、いろいろ改変。

npm パッケージを使うことになったので、JSベタ貼りではなくきちんとzipでアップロードすることになります。
となると、いろいろ面倒になってくるので、gulp 対応、CoffeeScript 化もついでに行います。

コード

package.json

使う npm パッケージは以下の通り。

  • moment-timezone: 有名な日時ライブラリの timezone 対応版。UTC-JST 変換、時刻判定に。
  • q: 有名な Deferred-Promise ライブラリ。
  • twilio: せっかく npm 使うので、直接 REST API を叩くのではなく、サクッとライブラリで。

以下、dev 用。

  • coffee-script: gulpfile を coffee で書くために。
  • del: clean task でのファイル削除用。
  • gulp: gulp 本体。
  • gulp-coffee: coffee compile 用。
  • gulp-zip: zip 圧縮用。
  • run-sequence: gulp task の順序制御用。
package.json
{
  "name": "lambda-twilio-alert",
  "version": "1.0.0",
  "description": "",
  "author": "",
  "license": "ISC",
  "dependencies": {
    "moment-timezone": "^0.4.0",
    "q": "^1.4.1",
    "twilio": "^2.2.1"
  },
  "devDependencies": {
    "coffee-script": "^1.9.3",
    "del": "^1.2.0",
    "gulp": "^3.9.0",
    "gulp-coffee": "^2.3.1",
    "gulp-zip": "^3.0.2",
    "run-sequence": "^1.1.1"
  }
}

gulpfile

gulpfile も適当にサクッと作成。

gulpfile.coffee
gulp = require 'gulp'
coffee = require 'gulp-coffee'
zip = require 'gulp-zip'
del = require 'del'
runSequence = require 'run-sequence'

gulp.task 'clean', (cb) ->
  del 'build', cb

gulp.task 'coffee', ->
  gulp.src 'src/*.coffee'
  .pipe coffee bare: true
  .pipe gulp.dest 'build'

gulp.task 'copy', ->
  gulp.src 'node_modules/@(moment-timezone|q|twilio)/**'
  .pipe gulp.dest 'build/node_modules'

gulp.task 'zip', ['coffee', 'copy'], ->
  gulp.src 'build/**'
  .pipe zip 'lambda.zip'
  .pipe gulp.dest 'dist'

gulp.task 'build', ->
  runSequence 'clean', 'zip'

lambda 用コード本体

本体はこんな感じで。
toNumbers には、配列で電話番号を列挙します。

TwiML については S3 Bucket 等の HTTP アクセス可能な場所に設置してもいいのですが、Twilio Labs に Twimlets なるものがあり、これの Echo を使うと QueryString で渡したデータをそのまま XML としてエコーバックしてくれるので、こちらを使っています。
Twimlets を使うことで、任意のメッセージを動的に喋らせることも簡単になりますね。

また、moment-timezone で Asia/Tokyo に変換、10時-19時の間はスキップするようにしています。

twilio-node は Q の Promise を返す仕組みを持っていますので、複数の makeCall の結果を Promise オブジェクトの配列で持ち、Q.allSettled で処理しています。

src/index.coffee
accountSid = 'YOUR_TWILIO_ACCOUNT_SID'
authToken = 'YOUR_TWILIO_AUTH_TOKEN'
fromNumber = '+81XXXXXXXXXX'
toNumbers = [
  '+81XXXXXXXXXX'
  '+81XXXXXXXXXX'
  '+81XXXXXXXXXX'
  '+81XXXXXXXXXX'
  '+81XXXXXXXXXX'
]
twiML = '''
<Response>
<Say language="ja-JP" voice="alice">サービス名 アラート通知です。</Say>
<Pause length="1" />
<Say language="ja-JP" voice="alice">障害が発生した可能性がありますので、アラートメール及びシステムの状態を確認して下さい。</Say>
</Response>
'''

https = require 'https'
queryString = require 'querystring'
moment = require 'moment-timezone'
Q = require 'q'
twilio = require 'twilio'
client = new twilio.RestClient accountSid, authToken

exports.handler = (event, context) ->
  try
    # Skip CloudWatch recovery notification
    if event.Records[0].Sns.Subject.match /^OK: /
      context.done null, 'Skip notification.'
      return
  catch e
    context.done null, 'Invalid event data.'
    return

  d = moment().tz('Asia/Tokyo')
  day = d.day()
  hour = d.hour()
  if day >= 1 and day <= 5 and hour >= 10 and hour <= 18
    # 平日(月〜金)の10時〜19時は通知を抑制する
    context.done null, 'Skip notification.'
    return

  console.log 'Start twilio call'
  promises = []
  for to in toNumbers
    promises.push client.makeCall
      from: fromNumber
      to: to
      url: "http://twimlets.com/echo?Twiml=#{queryString.escape twiML}"

  Q.allSettled(promises).then ->
    args = [].slice.apply arguments
    for arg in args
      if arg.state is 'rejected'
        context.done null, "Call failed! Reason: #{arg.reason}"
        return
    context.done null, 'Call succeeded.'
  return

ビルド、更新

gulp build すると、dist 以下に lambda.zip が生成されます。

あとは、AWS CLI を使って update を行います。

aws --region ap-northeast-1 lambda update-function-code --function-name YOUR_FUNCTION_NAME --zip-file fileb://dist/lambda.zip

テスト実行

AWS CLI の invoke でテストができます。Management Console から実行しても OK です。

aws --region ap-northeast-1 lambda invoke --function-name YOUR_FUNCTION_NAME lambda.log

追加予定のアイデア

  • SNS のメッセージ内容(重要度)によって電話のかけ先を変える
  • TwiML のメッセージ内容を具体的な障害内容にする
  • 休日カレンダーを考慮した業務時間の判定
    • このためだけに Google API 対応するのはちょっと面倒。。

  • 6/26 修正

    • 土日はアラートメールを見過ごす可能性が高いので、時間に関係なく電話をかけるように修正
  • 7/1 修正

    • AWS Lambda が東京リージョンで利用可能になったため、コマンドの region を修正
    • CloudWatch の recovery 通知は無視するよう修正
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
Sign upLogin
48
Help us understand the problem. What are the problem?