LoginSignup
16
13

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-01-25

概要

事前に設定したタイトル/作者の漫画の発売日になったら、自動的に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

2.AWS LamdaにてFunction登録

①AWS LambdaよりFunction(node.js)作成
スクリーンショット 2017-01-25 23.42.18.png

②blueprintは[Blank Function]を選択

③トリガーにCronを選択(キャプチャでは毎日9:00に設定)
 ※[Enable trigger]はチェックしない!
スクリーンショット 2017-01-25 18.27.34.png

④環境変数にSlckのHookUrl/Channelを指定
※[Encryption key]は IAM > 暗号化キーより作成
スクリーンショット 2017-01-25 18.49.52.png

⑤とりあえず保存

3.新刊ネットにてRSS生成

①新刊ネット( http://sinkan.net )にてアカウント作成

②キーワードを設定し、自分用のRSSを生成
スクリーンショット 2017-01-25 18.54.46.png

③設定の「まもなく発売の表示範囲」を 「0日前から、1日後まで」に設定
⇛「本日が発売日」である漫画をRSSに載せるため
スクリーンショット 2017-01-25 23.43.44.png

4.コード実装

ソースコード:https://github.com/kon-shou/lambda_manga

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

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によって同期処理を実行

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より取得

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を繰り返す

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する

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

tips

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

最後に

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

参考

16
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
13