AWS Lambdaからfacebookのタイムラインへ投稿する

これは[今年もやるよ！AWS Lambda縛り Advent Calendar 2015](http://qiita.com/advent-calendar/2015/lambda)の20日目の記事である。

----
AWS Lambdaからfacebookの自分自身もしくは特定ページのタイムラインへ投稿してみよう。ただ残念なことに、facebookにはincoming webhookの仕組みがない。そのためfacebookのアクセストークンを取得するにはかなり面倒な手順が必要となる。

# 検証環境
今回の検証は、以下の環境で実施した。

| |version|
|:--|:--|
|OS|Ubuntu 14.04.3 (3.13.0-71-generic)|
|python|2.7.10|
|pip|7.1.2|
|aws-cli|1.9.12|
|lambda-uploader|0.5.0|
|requests|2.8.1|
|facebook-sdk|0.4.0|

# AWS Lambda用IAMユーザとIAMロールを作成
[AWS Lambda Developers Guide](https://docs.aws.amazon.com/lambda/latest/dg/setting-up.html)等を参考に、AWS Lambda用IAM UserとIAM Roleを作成する。

## IAM User
今回は検証のため、**AdministratorAccess**権限を持ったIAM Userを作成した。本来はきっちり権限を制限した方が良い。

```json:AdministratorAccess
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "*",
      "Resource": "*"
    }
  ]
}
```

またこのユーザのAccess Keyを取得し、AWS CLIに設定した。

```text:awscli
$ aws configure list
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                <not set>             None    None
access_key     ****************J5NA shared-credentials-file    
secret_key     ****************uUMd shared-credentials-file    
    region           ap-northeast-1      config-file    ~/.aws/config
```

## IAM Role
今回のLambda Functionは他のAWSリソースにはアクセスしないため、CloudWatch Logsへ出力できる権限だけ持つ **lambda_basic_execution** を適用したIAM Roleを作成した。

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

後に必要となるため、**Role ARN** をメモしておく。

# Facebookに投稿するアクセストークンの取得
Facebookのアクセストークンを取得するには、結構面倒な手順（facebookアプリの作成 & OAuth コールバックを処理するhttp endpointの作成）が必要だ。これはAWS Lambdaとは関係無い話でもあるため、手順は[別のページ](http://qiita.com/nmatsui/items/4821146a19b614536c79)にまとめている。そのページを参考にしながら、User Access TokenとPage Access Tokenを取得して欲しい。

なお今回は、取得したアクセストークンをeventとしてLambda Functionに与える形を取っているが、リクエスト毎に変更されるものでもないため、S3やDynamoDBなどに保存しても良いだろう。

# Lambda Functionの作成
pythonを用いてLambda Functionを作成し、lambda-uploaderを用いてAWS Lambdaに登録する。

## Lambda Functionの設定ファイル作成
lambda-uploaderで用いる`lambda.json`を次のように作成する。

```json:lambda.json
{
  "name": "facebook_publisher",
  "description": "Post a message to facebook timeline",
  "region": "ap-northeast-1",
  "handler": "facebook_publisher.lambda_handler",
  "role": "arn:aws:iam::<AccountID>:role/lambda_basic_execution",
  "timeout": 300,
  "memory": 256 
}
```

メモしておいたIAM RoleのARNを`role`に設定すること。

## requirements.txtの作成
今回はFacebookのGraphAPIをラップする[Facebook SDK for Python](https://facebook-sdk.readthedocs.org/)を利用する。このFacebook SDK for Pythonは[requests](http://docs.python-requests.org/)に依存しているが、これら二つのライブラリを`requirements.txt`に記述しておけば、Lambda Functionと同梱してAWSにアップロードされる。

```text:requirements.txt
requests
facebook-sdk
```

## Lambda Functionの作成
トークンとメッセージをeventとして入力されると、自分自身もしくはトークンを取得したfacebookページにメッセージを投稿するLambda Function（facebook_publisher.py）を作成する。



```py:facebook_publisher.py
#!/usr/bin/env python
# -*- encode: utf-8 -*-

from __future__ import print_function

import json
import facebook


class Timeline:
    def __init__(self, user_token, page_token):
        self.user_endpoint = facebook.GraphAPI(user_token)
        self.page_endpoint = facebook.GraphAPI(page_token)

    def post_me(self, msg):
        self.user_endpoint.put_object('me', 'feed', message=msg)
        print('posted to my timeline: %s' % msg)

    def post_page(self, msg):
        self.page_endpoint.put_wall_post(message=msg)
        print('posted to page timeline: %s' % msg)


def lambda_handler(event, context):
    user_token = event['token']['user_access']
    page_token = event['token']['page_access']
    target = event['target']
    message = event['message']
    try:
        timeline = Timeline(user_token=user_token, page_token=page_token)
        if target == 'me':
            timeline.post_me(message)
        elif target == 'page':
            timeline.post_page(message)
        else:
            print('%s is invalid' % target)
            return False
    except Exception as e:
        print('[%s] %s' % (type(e).__name__, str(e)))
        return False

    return True
```

## AWS Lambdaへアップロード
labmda-uploaderを利用して、Lambda Functionと関連ライブラリをAWS Lambdaへアップロードする。

```text:
$ tree .
.
├── facebook_publisher.py
├── lambda.json
└── requirements.txt
$ 
$ lambda-uploader 
λ Building Package
λ Uploading Package
λ Fin
```

# AWS LambdaからFacebookのタイムラインへ投稿
では、AWS CLIからAWS Lambdaへイベントを発行し、AWS LambdaからFacebookへメッセージを投稿してみる。

## イベントファイルの作成
今回の検証では、他のサービスから呼び出されるのではなく、手動でイベントを発行するので、次のイベントファイル（event.json）を作成する。

```json:event.json
{
  "token": {
    "page_access": "<取得したFacebookのPage Access Token>", 
    "user_access": "<取得したFacebookのUser Access Token>"
  },
  "target": "<me|page> 自分のタイムラインに投稿する場合はme、facebookページに投稿する場合はpage",
  "message": "<投稿するメッセージ>"
}
```

## イベント発火
では、AWS CLIからイベントを発火させよう。

```
$ aws lambda invoke --invocation-type RequestResponse --function-name facebook_publisher --payload file://event.json output.txt
{
    "StatusCode": 200
}
```

発火時に指定した`output.txt`を確認すれば、Lambda Functionからの戻り値がわかる。

```text:output.txt
vagrant@trusty64:~/python/aws_lambda$ cat output.txt 
true
```

## 投稿結果
上記の手順により、自分自身のタイムラインやfacebookページのタイムラインへAWS Lambdaから投稿できた。

![スクリーンショット 2015-12-15 0.24.02.png](https://qiita-image-store.s3.amazonaws.com/0/37648/f30a05bb-4c51-0e37-caaa-3cd56f1cb03a.png)

<img width="1169" alt="スクリーンショット 2015-12-15 0.24.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/37648/4d80b657-8441-0923-dda7-e3adb49ca6c8.png">

AWS Lambdaの結果は、CloudWatch Logsから確認できる。

<img width="1392" alt="スクリーンショット 2015-12-15 0.28.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/37648/fe804c6a-77c3-a2fc-aa7c-a8bc500a5eea.png">

# まとめ
AWS Lambda以前の部分で苦労はあるものの、facebookの自分のタイムラインやページのタイムラインへAWS Lambdaからメッセージを投稿することができた。例えばreleaseタグをgithubへpushしたらfacebookページへ告知するなど、なんかイイカンジに使えないかと考えている。