LINE BOT APIを使ってAWS Lambdaでサーバレスな版画風写真加工BOTを作る(Node.js)

More than 1 year has passed since last update.

はじめに

4月7日にLINE BOT APIを公開してから約2ヶ月がたちました。
公開当初はLet's encryptやStartSSLなどの無料SSL証明書が利用できなかったり(4月18日に追加)、ホワイトリストIPアドレスの設定が必須で、Heroku等のPaaS系サービスを使った環境下では工夫が必要であったり(4月28日にオプションに変更)と一部問題もありましたが、現在はどちらも解消されています。
(参考: LINE Developer News)

そんなわけで、以前であれば多少面倒なこともあったLINE BOTの作成が手軽にできるようになったので、2016年6月頭時点ででている情報をまとめるという目的も兼ねて、最近何かと話題なAWS Lambdaを使ったサーバレスな環境で今更ながらLINE BOT(版画風画像加工BOT)を作成してみたいと思います。

QiitaでもすでにLINE BOTについての記事は相当な数でているので、今回はあまり見られなかった画像加工系のBOTを作成しBOT APIでの画像の受信/送信処理について詳しく説明していきます。

ちなみに今回の対象読者は以下を想定しています。

  • これからLINE BOTを作成しようと思っているヒト
  • LINE BOTを作成してみたいけどサーバをたてるのが面倒なヒト
  • LINE BOTで画像を使った何かをしてみたいヒト

版画風画像加工BOTのイメージ

版画風画像加工BOT完成イメージ

BOTシステムの構成図

今回はこのようなシステム構成でLINE BOTを構築していきます。
※ AWS LambdaとAmazon S3は利用料金がかかりますが、Lambdaは100万件/月まで無料で利用できます。
また、S3もAWSアカウントを作成してから1年以内なら5GBのストレージと2,000Putリクエストの無料枠があります。
(詳しいAWS LambdaとAmazon S3の料金は「AWS Lambda > 料金」「Amazon S3 > 料金」を参照ください)

版画風画像加工BOT完成イメージ

事前準備

以下のアカウントだけ事前に取得しておいてください。

LINE BOT API

LINE BOTの開発に必要なこと

LINE BOT開発者がLINE BOTを作るには最低限以下の作業が必要です。

  • LINE Business Centerの登録
  • BOT API Trialアカウントの取得
    ※ 現在LINEアカウントにつき1つのアカウントのみ取得可能
    ※ フレンド数上限50人 (申請することで2000人まで拡張可能)
  • SSL(https)による通信が可能なBOT Serverの構築
    ※ 4月18日にLet's encryptとStartSSLの無料SSL証明書を追加
    許可済み認証局リスト
  • リクエストを受け取るCallback URLの登録
    ※ SSL通信、POSTでアクセスできること
  • LINE BOT APIによるメッセージの送受信処理

LINE BOTの一般的な流れ

LINE BOT APIの仕組み

  1. ユーザがメッセージを送信 または BOTに対するオペレーション(友達登録/ブロック)を実行
  2. LINEプラットフォームがCallback URLに登録されたBOT Serverへリクエストを送信(コールバック)
  3. BOT Serverで受け取ったリクエストをあれこれ
    ※ 実際にはリクエストの署名認証などが必要ですが今回は省略します
    ※ (参考: Signature validation)
  4. LINE BOT APIを使ってユーザにメッセージを送信

BOT Serverの条件 〜SSL(https)〜

LINEプラットフォームからのBOT Serverへのコールバックはhttpsで通信されます。
(参考: Receiving messages/operations)
そのためCallback URLに登録するBOT ServerはSSLによる通信ができることが条件となります。

ただ、今回のようにAWS Lambdaを使えば、API Gatewayによりデフォルトでhttpsのエンドポイントが作成されるためSSLのための特別な作業も必要ありません。

ホワイトリストIPアドレスの登録

LINE BOT APIの仕組み

BOT ServerのIPアドレスをホワイトリストIPアドレスとして登録することによって、それ以外のIPアドレスからのアクセスを制限することができます。
LINE BOT APIの公開当初は必須項目であり、このためIPアドレスが固定でないPaaS系サービス等の利用には工夫が必要でした。
ただ現在はオプション項目となっており、必ずしもIPアドレスを登録する必要はなくなりました。

BOT API Trialアカウントで出来ること

現在はBOT APIアカウントはトライアル版でのみ提供されています。そのためトライアル版では以下の機能が利用可能です。

  • メッセージの送受信
  • リッチメッセージの送信

作成手順

1. AWS LambdaによるBOT Serverの構築 〜作成から公開(API Gateway)〜

AWS Lambda及びAmazon API GatewayについてはAmazonが公開している以下のドキュメントを参考にしてください。

ここではAWS Lambdaを使いサーバレスなBOT Serverを構築し、API Gatewayを利用してhttps通信可能なAPIエンドポイントを取得します。

1.1 Lambda関数の作成

AWSにサインインし、ホームから"Lambda"を選択。
AWS Console

右上のリージョンを確認し、任意のリージョンに変更。
"Get Started Now"からLambda関数を作成します。
AWS Console

BluePrintの選択です。AWS Lambdaでは事前に用意された多数の雛形(BluePrint)をつかってLambda関数の作成を開始することができます。
今回は特に使用しないで先に進みます。
BluePrintの選択

Lambda関数の各種設定をおこないます。
とりあえずランタイムをNode.js 4.3に、メモリとタイムアウト時間を1024MBと1分くらいにします。
関数の名前と説明は適当につけて大丈夫です。
※ 画像加工処理は時間がかかる処理なのでメモリとタイムアウト時間は適宜調整してください。
Lambda関数の設定

Lambda関数の作成
コードのアップロード方法を選択し、Lamda関数を作成します。

  • Edit code inline...AWS Lambdaのコンソールで提供されるエディタで直接作成
  • Upload a ZIP file...ローカルのファイルをZipで圧縮しアップロード
  • Upload a file Amazon S3...Amazon S3からファイルをアップロード

Edit code inlineではAWS Lambdaに事前にインストールされたAWS-SDKライブラリ及び一部ライブラリ(ランタイムにより異なる)のみが利用できます。
それ以外のライブラリの使用や、AWS CLIを利用したい場合はUpload a ZIP fileまたはUpload a file Amazon S3を選択します。
(参考: Lambda 実行環境と利用できるライブラリ)

ここでは一旦Edit code inlineを選択し、以下の最低限のコードのみを作成しておきます。
Lambda関数の作成

次にHandlerとRoleの設定をします。
HandlerとRoleの設定

Handlerの指定
HandlerはAWS Lambdaによって呼び出される関数です。AWS LambdaはこのHandlerを呼び出してイベントデータ(event)やランタイム情報(context)を受け渡します。
上の例ではexports.lambdaHandlerとしてHandlerを指定します。

Roleの設定
Lambda関数の実行権限の設定です。権限にはIAMロールを使用します。
(参考: IAMとは)

まずは最低限の実行権限であるBasic execution roleを選択すると、IAM Managerが開きます。
Lambda関数の作成

適当な名前をつけて先に進んでいくと、以下の様な画面が表示されLambda関数は正常に作成されます。
Lambda関数の作成成功

1.2 Lambda関数の公開

AWS LmabdaはAPI GatewayによりRESTfulなAPIエンドポイントを作成することできます。
また、API Gatewayではデフォルトでhttpsエンドポイントが作成されるためSSL化のための特別な作業は必要ありません。

まずはAWS LambdaのAPI endpointsタブから"Add API endpoint"を選択。
APIエンドポイントを追加する

"API Gateway"を選択しAPIエンドポイントの設定を行います。
Lambda関数の作成成功

Method
LINEプラットフォームはBOT ServerへPOSTでコールバックするため、MethodをGETからPOSTに変更します。
(参考: Receiving messages/operations)

Security
APIのアクセス制御の設定を行います。設定にはOpen, API Key, IAMを指定できますが今回はあまり考えずLINEプラットフォームから呼び出せるようにOpenに変更します。

以上の設定を変更し、"Submit"を選択するとAPIエンドポイントが作成されます。
APIエンドポイントの作成成功

2. BOT Serverの登録

2.1 コールバックURLの登録

以下のサイトから"Channels"を選択します。
https://developers.line.me/
Channelsに移動

アカウントがBOT API Trialアカウントになっていることを確認し、Basic informationからEditを選択。

Callback URLの項目に、先ほどAPI Gatewayで取得したAPIエンドポイントURLをポート443を指定して設定します。
※ 現在ポートに443を指定しないとエラーになります。
(エンドポイントURLがhttp://serverdomain.com/callbackであればhttp://serverdomain.com:443/callbackと指定する)
Callback URLの登録

2.2 テスト送信

正しくメッセージリクエストがBOT Serverにコールバックされるかテストしてみます。
当然まだBOTからの返信はありません。
LINE BOTへのテスト送信

2.3 ログの確認 ~Amazon CloudWatch~

AWS Lambdaでは標準でAmazon CloudWatchと連携しており、ログをCloudWatchに書き込むことができます。
Node.jsでもconsole.log()console.error()を使ってCloudWatchにログを出力できます。
(参考: ログ作成 (Node.js))

AWSコンソールホームからCloud Watchを選択します。
CloudWatchを開く

"ログ"を選択後表示されるLambda関数のロググループを選択。
ログの選択

イベント時刻から最新のログを選択。
ログストリームの選択

表示されたログの中身を確認します。
eventオブジェクトの中には先ほど送信したメッセージが含まれているプロパティを確認できます。
ログの確認

3 LINE BOTの作成

3.1 AWS Lambdaの準備 〜Lambdaで標準ライブラリ以外を利用する〜

今回のBOTでは以下のモジュールを利用します。

  • https・・・https通信
  • aws-adk・・・Amazon S3の操作
  • gm・・・画像処理。node-imagemagickより派生しておりパフォーマンスが高い
  • async・・・同期/非同期の制御

また、Lambda(Node.js)では標準モジュール以外に以下のモジュールのみが事前にインストールされています。
(参考: Lambda 実行環境と利用できるライブラリ)

今回は上記以外のモジュールを利用したいので、ローカルでデプロイパッケージを作成しAWS Lambdaにアップロードする方式をとります。
(参考: デプロイパッケージの作成(Node.js))

任意のディレクトリを作成し移動

mkdir lambda
cd lambda

npmコマンドでgm及びasyncモジュールをインストール

npm install gm async

Lambda関数を含むjsファイル(filename.js)を作成します

exports.lambdaHandler = function(event, context){
  //省略
};

フォルダの中身をzipで圧縮しデプロイパッケージを作成

zip -r ./zipname.zip filename.js node_module

AWS Lambdaでcode typeからUpload a .ZIP fileを選択し、先ほどのzipファイルをアップロードします。
zipファイルのアプロード

ConfigurationタブからHandlerを変更します。
もともとのハンドラがexports.lambdaHandlerの場合は(filename).lambdaHandlerに変更します。

3.2 テキストメッセージの受信

コールバックURLの登録で、すでにLINEプラットフォームからのメッセージの受信は完了しています。
そこで、ここでは受け取ったリクエストbodyからユーザのメッセージ(テキスト)を抽出していきます。

BOT APIリファレンスを参照すると、LINEプラットフォームからコールバックされるリクエストbodyにはresultプロパティが含まれており、そのresultプロパティに配列としてメッセージ情報が格納されていることがわかります。
(参考: API reference : Getting started with BOT API Trial)

ただここで注意しなければならないのはresultプロパティには1つ以上のメッセージが含まれている可能性があることです。
リファレンスによれば同時に最大100件のメッセージが含まれる可能性があるため、resulatプロパティの配列から1つずつメッセージを取得していく必要があります。
(※ result[0]で最初の要素のみを取り出してはいけない)

exports.lambdaHandler = function(event, context){
  // Retrieve each message. max:100
  event.result.forEach( function(message, index){
    console.log(JSON.stringify(message));
  });
};

各メッセージには以下のプロパティが含まれています。
(参考: API Reference Property list of messages)

プロパティ 説明
from 固定値
fromChannel 固定値
to BOTのMID
toChannel BOTのチャンネルID
eventType データの種類(メッセージかオペレーション)
content メッセージの中身

ここで重要なのはeventTypecontentです。
eventTypeでは受け取ったリクエストがメッセージかオペレーションかを判定することができ、
contentではメッセージ及びオペレーションの情報を取得できます。
メッセージの場合のcontentの中身は以下になります。

プロパティ 説明
id メッセージID
contentType メッセージの種類
from ユーザのMID
createdTime リクエストが生成された日時
to メッセージを受け取るユーザのMID(配列)
toType メッセージを受け取るユーザのタイプ
contentMetadata メッセージの詳細
text テキストメッセージ(最大10000字まで)
location 位置情報

contentTypeではメッセージの種類(テキストか画像か等)を判別することができます。
fromは、後々ユーザにメッセージを送信する際に必要です。
textは、contentTypeの値が1(テキスト)の時にテキストメッセージの内容が格納されます。

すなわちテキストメッセージは基本的に以下の流れで取得できます。

  1. resultプロパティから各メッセージを取得
  2. 各メッセージのcontent.contentTypeプロパティの値を確認
  3. contentTypeが1(テキスト)であれば、content.textプロパティの値を取得

3.3 テキストメッセージの送信

LINE BOT APIを使ってユーザにテキストメッセージを送信します。
ここではテキストを送ったユーザに対して「画像を送ってね」と返します。

まずLINE APIを叩くためのヘッダ情報とエンドポイントホストです。
このヘッダ情報とエンドポイントホストは全てのBOT APIコールで共通です。
また、こちらのチャンネルIDとChannel SecretはコールバックURLを登録した際と同様に、LINEのチャンネルコンソール > Basic informationで確認できます。

Header情報
var endpointHost = 'trialbot-api.line.me';  // End Point(Fixed value)
var headers      = {
  'Content-Type':                 'application/json; charset=UTF-8',  //Fixed value
  'X-Line-ChannelID':             'YOUR_CHANNEL_ID',                  //Your channel ID
  'X-Line-ChannelSecret':         'YOUR_CHANNEL_SECRET',              //Your channel secret
  'X-Line-Trusted-User-With-ACL': 'u5b8d5cd3e8f9baf9d7efe0692de87e75' //Fixed value
}

ヘッダに含める内容以外では、メッセージの送信には以下が必要です。
(参照: LINE API Reference | メッセージ送信)

またリクエストbodyには以下の内容を含める必要があります。ただし送信先ユーザのMIDと送信テキスト以外は固定値です。

var body = JSON.stringify({
  to:        [mid],                   //NOTE: 'to' parameter require array of mids
  toChannel: 1383378250,              //Fixed value
  eventType: "138311608800106203",    //Fixed value
  content: {
    contentType: 1,       //Fixed value if send text
    toType:      1,       //Fiexd value if send text
    text:        text
  }
});

実際の実装では、メッセージの送信処理をsendTextTo関数に集約し引数としてMIDとtextを渡しています。
これでsendTextTo関数により簡単にメッセージを送れるようになりました。

3.3 画像メッセージの受信

画像メッセージの受信フローはテキストとやや異なります。
というのもユーザが画像を送った時に受け取るリクエストには画像データは含まれておらず、
再度画像取得のためのAPIを叩くことではじめて画像データを取得できます。

画像取得のためのAPIは以下が必要です。

こちらも実装ではretrieveImageFrom関数を作成し引数にcontentIdを渡すようにしています。
また、取得は非同期に行われるのでcallback関数も渡します。

画像データの取得
http(s)モジュールでは複数回にわけてデータを取得する可能性があるため、取得したデータは順次data配列に格納しておき、
全てのデータを取得した段階でそれら結合する必要があります。

var data = [];
res.on('data', function(chunk){
  //image data dividing it in to multiple request
  data.push(new Buffer(chunk));
}).on('error', function(err){
  console.log(err);
}).on('end', function(){
  img = Buffer.concat(data);
  callback(null, img);
});

3.4 サムネイルの作成と保存 ~Amazon S3との連携~

LINE APIで画像メッセージをユーザに送信するためには最低限以下のものが必要になります。
(参照: LINE API Reference | 画像の送信)

  • 画像のオリジナルURL ※ 1024×1024以下
  • 画像のサムネイルURL ※ 240×240以下
  • 送信するユーザのMID

サムネイルをgmのリサイズ処理で作成、画像(オリジナル及びサムネイル)をS3に保存し画像のURLを得ます。
また、ここでは速度向上のためasyncモジュールによりオリジナル画像の保存及びサムネイルの作成・保存は非同期に実行させます。

GraphicsMagick(gm)を使った画像の加工 〜リサイズ処理〜
GraphicsMagickについては以下を参考にしてください。

画像加工はメソッドチェーンにより複数の画像処理を組み合わせることができます。
また、加工後のデータをイメージバッファーとして取得する場合はメソッドチェーンの最後にtoBufferメソッドを呼び出します。

gm(img).resize(240).toBuffer('jpg',function(err,buf){
  if(err){
  } else {
  }
});

Amazon S3への画像の保存
保存のためのS3バケットを作成します。
AWS コンソールホームからS3を選択します。
S3の選択

"バケットを作成"を選択。
Bucketを作成

バケット名とリージョンを選択して作成します。
※ バケット名は一意な名前である必要があります。
(参照: S3 ドキュメント)
Bucketの設定

これでAmazon S3の準備は完了です。

S3をLambdaから利用するにはAWS-SDKモジュールを利用します。
LambdaではAWS-SDKが標準でインストールされており、アクセス権限もデフォルトでついてきます。そのためIAMロールを適切に与えるだけで利用することが可能です。
(参考: AWS リソースの管理に関するポリシーの例))
(参考: Amazon S3 のアクションと条件コンテキストキー)

Lambda関数を作成するときに作ったIAMロールのポリシーを変更します。

AWSコンソールホームからIAMを選択。
IAMの選択

"ロール"を選択すると先ほど作成したロールがあるので選択します。
Roleの選択

"アクセス許可"タブの"インラインポリシー"の項の"ポリシーの編集"を選択。
Policyの選択

以下のポリシーに変更し、S3へのバケットの保存権限とバケットのアクセスコントロール(ACL)権限を付与して保存します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:PutObjectAcl"
            ],
            "Resource": [
                "arn:aws:s3:::*"
            ]
        }
    ]
}

コードからS3にアクセスするにはバケット名とリージョン名、リージョンエンドポイントが必要になります。
また、S3バケットの各オブジェクトにアクセスするURLはリージョンエンドポイント/バケット名/オブジェクト名です。
リージョンエンドポイントとリージョン名は以下を参考にしてください。
(参考: AWS のリージョンとエンドポイント)

var s3     = new aws.S3({ apiVersion: '2006-03-01',     //Fixed value
                          region:     'ap-northeast-1'  //Your region name
});
var bucket = 'YOUR_BUCKET_NAME';                        //Your bucket name
var s3Url  = 'https://s3-ap-northeast-1.amazonaws.com/' + bucket + '/'

実際の実装では、S3に画像を保存するsaveImageToS3関数を作成し、画像データ、ファイル名を引数としてわたしS3バケットに保存しています。
また、こちらも保存が完了したタイミングでメッセージを送信する必要があるのでcallback関数も合わせて引数に渡します。

3.5 画像メッセージの送信

画像メッセージの送信は基本的にテキストメッセージの送信と同じです。
(参考: LINE API Reference | 画像の送信)

ヘッダとメッセージには以下を指定をします。

S3への画像の保存後に、リクエストbodyに以下の内容を含めてAPIをコールすることで画像を送信できます。

var body = JSON.stringify({
  to:        [mid],
  toChannel: 1383378250,            //Fixed value
  eventType: "138311608800106203",  //Fiexd value
  content: {
    contentType: 2,     //Fiexed value if you send image
    toType:      2,     //Fiexed value if you send image
    originalContentUrl: originalUrl,
    previewImageUrl:    previewUrl
  }
});

3.6 版画風への加工処理

こちらの画像処理を画像(オリジナルとプレビュー)をS3へ保存する前におこないます。
なんちゃって版画風への画像処理であればグレースケール化後に少し広めなフィルタ半径(radius)を使ってエッジ処理するだけです。
(もちろん本格的に版画風にしようとなるともっと複雑です....)

あとはリサイズ時と同じでgmオブジェクトを使ってグレースケール化にmodulateメソッドを、エッジ処理にedgeメソッドを利用します。

gm(img).modulate(100,0).edge(7).toBuffer('jpg',function(err, buf){
    if(err){
    } else {
    }
});

3.7 完成

完成した画像加工BOTのソースコードです。
これにより最初の完成イメージどおりに画像を送ってくると版画風にしてくれるBOTが完成しました。

var https = require('https');                              //HTTPS
var async = require('async');                              //Syncronize
var gm    = require('gm').subClass({ imageMagick: true }); //Image processing
var aws   = require('aws-sdk');                            //AWS SDK

/* LINE API */
var endpointHost = 'trialbot-api.line.me';  //End Point(Fixed value)
var headers      = {
  'Content-Type':                 'application/json; charset=UTF-8',  //Fixed value
  'X-Line-ChannelID':             'YOUR_CHANNEL_ID',                  //Your channel ID
  'X-Line-ChannelSecret':         'YOUR_CHANNEL_SECRET',              //Your channel secret
  'X-Line-Trusted-User-With-ACL': 'u5b8d5cd3e8f9baf9d7efe0692de87e75' //Fixed value
};

/* AWS SDK */
var s3     = new aws.S3({ apiVersion: '2006-03-01',     //Fixed value
                          region:     'ap-northeast-1'  //Your region
});
var bucket = 'YOUR_BUCKET_NAME';                        //Your bucket name
var s3Url  = 'https://s3-ap-northeast-1.amazonaws.com/' + bucket + '/';

/* Other */
var extension = '.jpg';         //Image extention


function sendTextTo(mid, text){
  var options = {
    hostname: endpointHost,
    path:     '/v1/events',
    headers:  headers,
    method:   'POST'
  };
  var req = https.request(options, function(res){
    res.on('data', function(chunk){
    }).on('error', function(err){
        console.log(err);
    }).on('end', function(){            //call when no more date in response
        console.log('finish sending text message')
    });
  });
  var body = JSON.stringify({
    to:        [mid],                   //NOTE: to parameter require array of mids
    toChannel: 1383378250,              //Fixed value
    eventType: "138311608800106203",    //Fixed value
    content: {
      contentType: 1,       //Fixed value if send text
      toType:      1,       //Fiexd value if send text
      text:        text
    }
  });

  req.write(body);
  req.end();
}

function retriveImageFrom(contentId, callback){
  var options = {
    hostname: endpointHost,
    path:     '/v1/bot/message/' + contentId + '/content',
    headers:  headers,
    method:   'GET'
  };
  var req = https.request(options, function(res){
    var data = [];
    res.on('data', function(chunk){
      //image data dividing it in to multiple request
      data.push(new Buffer(chunk));
    }).on('error', function(err){
      console.log(err);
    }).on('end', function(){
      console.log('finish to retrive image')
      img = Buffer.concat(data);
      callback(null, img);
    });
  });

  req.end();
}

function saveImageToS3(img, name, callback){
  var params = {
    Bucket: bucket,
    Key:    name,
    ACL:    'public-read',
    Body:   img
  };
  s3.putObject(params, function(err, data){
    if(err){
      callback("e", "");
    } else {
      callback(null, s3Url + name);
    }
  });
}

function sendImageTo(mid, originalUrl, previewUrl){
  var options = {
    hostname: endpointHost,
    path:     '/v1/events',
    headers:  headers,
    method:   'POST'
  };
  var req = https.request(options, function(res){
    res.on('data', function(chunk){
    }).on('error', function(err){
      console.log(err);
    }).on('end', function(){            //call when no more date in response
      console.log('finish sending image');
    });
  });
  var body = JSON.stringify({
    to:        [mid],
    toChannel: 1383378250,            //Fixed value
    eventType: "138311608800106203",  //Fiexd value
    content: {
      contentType: 2,     //Fiexed value if send image
      toType:      2,     //Fiexed value if send image
      originalContentUrl: originalUrl,
      previewImageUrl:    previewUrl
    }
  });

  req.write(body);
  req.end();
}

exports.lambdaHandler = function(event, context){
  // Retrieve each message. max=100
  event.result.forEach( function(message, index){
    console.log(JSON.stringify(message));
    var mid = message.content.from;
    switch(message.content.contentType){
      case 1:   // Text Message
        sendTextTo(mid, '画像を送ってね');
        break;
      case 2:   // Image Message
        sendTextTo(mid, 'ちょっとまってね');
        async.waterfall([
          function(callback){
            retriveImageFrom(message.content.id, callback);
          },
          function(img, callback){
            //convert image
            gm(img).modulate(100,0).edge(7).toBuffer('jpg',function(err, buf){
                if(err){
                  console.log(err);
                }
                callback(null, buf);
            });
          },
          function(img, callback){
            //save converted image and its thumbnail to S3 bucket
            async.parallel({
              original: function(callback){
                saveImageToS3(img, mid + extension, callback);
              },
              preview: function(callback){
                gm(img).resize(240).toBuffer('jpg',function(err,buf){
                  saveImageToS3(buf, mid + '_thumbnail' + extension, callback);
                });
              }
            }, function(err, result){
              if(err){
                console.log(err);
              } else {
                callback(null,
                         result.original,
                         result.preview);
              }
            });
          },
          function(originalUrl, previewUrl, callback){
            //send converted image
            sendImageTo(mid, originalUrl, previewUrl);
          }
        ], function(err, result){
          if(err){
            console.log(err);
          }
        });
        break;
      default:  // Other Messages
        sendTextTo(mid, '画像を送ってね');
        break;
    }
  });
};

現状の問題点とまとめ

今回AWS Lambda+API Gatewayを使ってサーバレスに版画風に画像を変換するLINE BOTを作成しました。
ただ、現状のLINE BOTでは少なくとも以下の問題点があります。

  • 画像処理に時間がかかる(画像を返信するのに約5秒ほどかかります)
  • ユーザ毎にファイル名をわけてオリジナルとプレビューの画像を一枚ずつS3に保存しているので複数同時に送られてきた場合に対処できない。
  • エラー処理や例外処理をしていない
  • ユーザからのオペレーション(登録/ブロック)に対応していない

これら問題点を解消していけばユーザが加工方法を指定して、色々な画像に加工してくれるBOTなどが作ることができそうです。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.