Lambdaで作る「漫画の発売日」をSlackに通知するシステム

#概要
事前に設定したタイトル/作者の漫画の発売日になったら、自動的にSlackに通知が来る

#データ経路
RSS(新刊ネット)=>AWS Lambda=>Slack

#作業手順
1.Lambda用RoleをIAMで作成
2.AWS LamdaにてFunction登録
3.新刊ネットにてRSS生成
4.コード実装

#1.Lambda用RoleをIAMを作成

IAMよりLambdaで使用するロールを作成
（"Lambda"という文字を辿ってポチポチして完了）
![スクリーンショット 2017-01-25 23.36.28.png](https://qiita-image-store.s3.amazonaws.com/0/158622/774ac6f9-3cfb-16b4-2ec2-e17b61f577ea.png)

#2.AWS LamdaにてFunction登録
①AWS LambdaよりFunction(node.js)作成
![スクリーンショット 2017-01-25 23.42.18.png](https://qiita-image-store.s3.amazonaws.com/0/158622/a6eb3fad-9b20-050a-571e-87b4b702edd3.png)

②blueprintは[Blank Function]を選択

③トリガーにCronを選択（キャプチャでは毎日9:00に設定）
　※[Enable trigger]はチェックしない！
![スクリーンショット 2017-01-25 18.27.34.png](https://qiita-image-store.s3.amazonaws.com/0/158622/acefe2e0-aeb8-33aa-2954-47ca61d84741.png)

④環境変数にSlckのHookUrl/Channelを指定
  ※[Encryption key]は　IAM > 暗号化キーより作成 
![スクリーンショット 2017-01-25 18.49.52.png](https://qiita-image-store.s3.amazonaws.com/0/158622/4b96dadb-74a6-861d-9e5b-fcd3f6c2c8c4.png)

⑤とりあえず保存

#3.新刊ネットにてRSS生成
①新刊ネット（ http://sinkan.net ）にてアカウント作成

②キーワードを設定し、自分用のRSSを生成
![スクリーンショット 2017-01-25 18.54.46.png](https://qiita-image-store.s3.amazonaws.com/0/158622/8ccbaad3-c2ce-fc4e-7aeb-3f65eccd6b6a.png)

③設定の「まもなく発売の表示範囲」を　「0日前から、１日後まで」に設定
⇛「本日が発売日」である漫画をRSSに載せるため
![スクリーンショット 2017-01-25 23.43.44.png](https://qiita-image-store.s3.amazonaws.com/0/158622/f52f3c57-233b-b4fc-8804-3f6a3333f504.png)

#4.コード実装
ソースコード：https://github.com/kon-shou/lambda_manga

①RSS取得を設定
pubTitleArr[]にタイトルを格納、pubLinkArr[]に商品リンクを格納

```Javascript:index.js
const fetchOptions = {
    uri: '新刊ネットのRSSのURL',
    transform: function (body) {
        let $ = cheerio.load(body ,{xmlMode : true});
        $("channel > item").each(function(i) {
          pubTitleArr[i] = $(this).find("title").text();
          pubLinkArr[i] = $(this).find("link").text();
          console.log(pubTitleArr);
          console.log(pubLinkArr);
        });
    }
};
```

②AWSよりRSS取得を実行
  nodejsは非同期処理なので、request-promiseによって同期処理を実行

```Javascript:index.js
exports.handler = (event, context, callback) => {
    rp(fetchOptions)
    .then(function ($) {
        processEventWithHookUrl (event, callback);
    })
    .catch(function (err) {
        console.log("Error Fetch API");
    });
};
```

③HookUrl/ChannelをAWS kmsより取得

```Javascript:index.js
function processEventWithHookUrl (event, callback) {
    if (hookUrl) {
        // Container reuse, simply process the event with the key in memory
        processEvent(event, callback);
    } else if (kmsEncryptedHookUrl && kmsEncryptedHookUrl !== '<kmsEncryptedHookUrl>') {
        const encryptedBuf = new Buffer(kmsEncryptedHookUrl, 'base64');
        const cipherText = { CiphertextBlob: encryptedBuf };

        const kms = new AWS.KMS();
        kms.decrypt(cipherText, (err, data) => {
            if (err) {
                console.log('Decrypt error:', err);
                return callback(err);
            }
            hookUrl = data.Plaintext.toString('ascii');
            processEvent(event, callback);
        });
    } else {
        callback('Hook URL has not been set.');
    }
}
```

④通知(1)
pubTitleArrの長さで「発売タイトルの有無」を判定
複数タイトルの場合はpostMessageを繰り返す

```Javascript:index.js
function processEvent(event, callback) {
  if (pubTitleArr.length > 0) {
    for (let i = 0; i < pubTitleArr.length; i++) {
      slackMessage = {
        text: `${pubTitleArr[i]}\n${pubLinkArr[i]}`,
      };
      console.log(slackMessage);

      postMessage(slackMessage, (response) => {
        if (response.statusCode < 400) {
            console.info('Message posted successfully');
            callback(null);
        } else if (response.statusCode < 500) {
            console.error(`Error posting message to Slack API: ${response.statusCode} - ${response.statusMessage}`);
            callback(null);  // Don't retry because the error is due to a problem with the request
        } else {
            // Let Lambda retry
            callback(`Server error when processing message: ${response.statusCode} - ${response.statusMessage}`);
        }
      });
    }
  } else {
    callback(`本日発売の漫画はありません`);
  }
}
```

⑤通知(2)
messageをjsonにしてHTTPメソッドでPOSTする

```Javascript:index.js
function postMessage(message, callback) {
    const body = JSON.stringify(message);
    const Sendoptions = url.parse(hookUrl);
    Sendoptions.method = 'POST';
    Sendoptions.headers = {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(body),
    };

    const postReq = https.request(Sendoptions, (res) => {
        const chunks = [];
        res.setEncoding('utf8');
        res.on('data', (chunk) => chunks.push(chunk));
        res.on('end', () => {
            if (callback) {
                callback({
                    body: chunks.join(''),
                    statusCode: res.statusCode,
                    statusMessage: res.statusMessage,
                });
            }
        });
        return res;
    });

    postReq.write(body);
    postReq.end();
}
```

#通知完了
![スクリーンショット 2017-01-26 0.42.44.png](https://qiita-image-store.s3.amazonaws.com/0/158622/07c9b331-86c1-e20e-c884-8150463ec7a8.png)

#tips
testコード(上記レポジトリのtest.js)を作っておくと、
node test.js
でスクリプト起動できるので楽

#最後に
Javascript/Lambda初心者が作ったので間違いがあるかもしれませんが、その時はご指摘をお願いします。

#参考
http://www.bokukoko.info/entry/exec_aws_lambda_in_local