AWS Lambda+YoutbeAPIでPerfumeのMVを定期ポストするTwitter botを作成してみた(Node.js)

AWS Lambdaには昨年、コードをcronのように定期実行できるスケジュール機能ができました。
ってことは簡単にTwitterのBotができる!

ということで試しにYoutbeにあるPefumeのMVを定期ポストするTwitter botを作成してみたのでメモ。

# 作成したもの

コードは以下におきました。

[toshihirock/lambda-tweet-perfume](https://github.com/toshihirock/lambda-tweet-perfume)

別途、Twitter及びGoogleのAPI Keyは設定する必要があります。

作成したbotはこちらです。現在、1日に1回ランダムで公式のMVを一つポストします。あんまり面白くなかったので、適当に消します。。。

[Twitter perfume_random_mv](https://twitter.com/random_pefume)


# ツイートの方法を確認する

Twitter公式のドキュメントを見てみます。

[Developers/Documentation/Overview](https://dev.twitter.com/overview/documentation)

色々書いてあるのでぱっと見で分かりにくいのですが、ツイートをするにはREST APIを使って実施することができそうです。また、認証にはOAuth2かAPI Keyを使う必要があるようです。

ツイートするには以下のAPIを使う必要がありそうです。

[POST statuses/update](https://dev.twitter.com/rest/reference/post/statuses/update)

# Twitter App登録を行う

ドキュメントにも書いてありますが、REST APIを使うには認証を行う必要があり、まずはTwitterにappの登録が必要なのようなのでやってみます。

以下リンクから遷移します。

[Twitter Apps](https://apps.twitter.com/)


![Screen Shot 2016-01-11 at 8.50.15 AM.png](https://qiita-image-store.s3.amazonaws.com/0/20193/28a8a2c0-f015-f12d-5ca6-dc7df855db0e.png "Screen Shot 2016-01-11 at 8.50.15 AM.png")

上記画面の **Create New App** を選択すると以下の画面が表示されるます。



![Screen Shot 2016-01-11 at 8.51.48 AM.png](https://qiita-image-store.s3.amazonaws.com/0/20193/2e792282-8170-e959-5492-c1fe991e371f.png "Screen Shot 2016-01-11 at 8.51.48 AM.png")


![Screen Shot 2016-01-11 at 8.57.08 AM.png](https://qiita-image-store.s3.amazonaws.com/0/20193/a2272153-c9e2-8221-2b49-6d68634ae90e.png "Screen Shot 2016-01-11 at 8.57.08 AM.png")

Name,Description,Websiteを適当に入れます。
自分の場合、以下のような感じ。

+ Name->tweet-aws-lambda
+ Description->tweet on AWS Lambda
+ Website->https://github.com/toshihirock（何でも良い)

入力後、画面下部にある **Yes,I agree** にチェックを入れ規約に同意し、 **Create your Twitter** を選択します。

作成後、表示される画面で **Key and Access Tokens** タブを設定し、画面下部を確認するとREST API利用時に必要なAccess Tokenがないので、 **Create my access token** を選択し、作成します。

![Screen Shot 2016-01-11 at 9.09.24 AM.png](https://qiita-image-store.s3.amazonaws.com/0/20193/9fbad7f1-59ec-7329-1bff-9bc40edc6ca6.png "Screen Shot 2016-01-11 at 9.09.24 AM.png")


作成後、アクセストークンが表示されていればOKです。

# cURLでツイートしてみる

一応、作成したアクセストークンが利用できるか確認してみます。
以下にアクセスし、curlで自分のプロフィール情報を取得してみましょう。

[GET users/show](https://dev.twitter.com/rest/reference/get/users/show)

上記ページの真ん中ぐらいにOAuth Signature Generatorと表示されている部分があります。

![Screen Shot 2016-01-11 at 9.44.45 AM.png](https://qiita-image-store.s3.amazonaws.com/0/20193/1e75a160-3cff-620c-b7c9-6cd74711a21c.png "Screen Shot 2016-01-11 at 9.44.45 AM.png")

上記セレクトボックスで先ほど作成したアプリ名を選択するとページが遷移します。選択しているAPIを叩くためのコマンドを生成してくれます(今回はユーザー情報取得用API)

Request query:となっている部分を **status=hello from cURL** という感じでツイートしたい内容に変更し、 **Get OAuth Signature** を選択します。

すると下部に **cURL command** という部分で設定した情報を取得するコマンドが表示されるのでコピペしてterminalで実行してみます。

```
$curl --request 'POST' 'https://api.twitter.com/1.1/statuses/update.json' --data 'status=hello+from+cURL' --header 'Authorization: OAuth oauth_consumer_key="xxxx", oauth_nonce="xxxxxx", oauth_signature="xxxxxx", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1452474781", oauth_token="xxxx", oauth_version="1.0"' --verbose
```

成功すればツイートが出来ているかと思います。

# Node.jsからツイートしてみる

次にNode.jsからツイートを行ってみます。

なお、事前に先ほどのリクエスト時に利用した、以下の値についてメモをしておきます。(Twitter Appsページで確認可能)

+ Consumer Key (API Key)
+ Consumer Secret (API Secret)
+ Access Token
+ Access Token Secret


APIを使ってツイートしても良いのですが、既にラッパーの良さそうなnpmがあったので今回はこちらを使ってみます。

[npm twitter](https://www.npmjs.com/package/twitter)

適当なディレクトリを作成して、パッケージをインストールします。

```
$mkdir node-tweet
$cd node-tweet
$npm install twitter
```

試しに自分のプロフィール情報を確認するコードを書いてみます。
**toshihiock** となっている部分は適宜自分のtwitterアカウントの @hoge の部分に置き換えてください。また、各キーも置き換えてください。

```lang:index.js
var Twitter = require('twitter');

var client = new Twitter({
  consumer_key: 'xxx',
  consumer_secret: 'xxxx',
  access_token_key: 'xxxx',
  access_token_secret: 'xxxx
});

var params = {screen_name: 'toshihirock'};
client.get('users/show', params, function(error, result, response){
  if (!error) {
    console.log(result);
    console.log(response);
  } else {
    console.log(error);
  }
});
```

実行してみます。

```
$node index.js
```

取得が成功すればconsole.logで自分のプロフィールが表示されます。エラーの場合、エラーの内容も表示されるので確認しましょう。

次にツイートを同じ要領でやってみます。
なお、ツイートはGETではなく、POSTなので **client.post** と変更する必要がある点は注意です。

```lang:index.js
var Twitter = require('twitter');

var client = new Twitter({
  consumer_key: 'xxx',
  consumer_secret: 'xxxx',
  access_token_key: 'xxxx',
  access_token_secret: 'xxxx
});

var params = {status: 'hello from Node!'};
client.post('statuses/update', params, function(error, result, response){
  if (!error) {
    console.log(result);
  } else {
    console.log(error);
  }
});

```

同じように実行し、 **hello from Node!** とツイート出来ていれば成功です!

# キー情報を別ファイルに設定する

Twitterのキー情報がコードに直書きとなっているため、このままGitHubなどへアップロードすると問題があります。そのため、キー情報を別のファイルに記載し、コードではその内容を利用するようにします。また、キー情報のファイルはGitHubへはアップロードしないようにします。

上記を簡略化するためにdotenvというnpmを使いします。

[motdotla/dotenv](https://github.com/motdotla/dotenv)

```
$npm install dotenv
```

最初に`.env`というファイルを作成し、こちらにキー情報を記載します。

```lang:.env
CONSUMER_KEY=xxx
CONSUMER_SECRET=xxxx
ACCESS_TOKEN_KEY=xxxx
ACCESS_TOKEN_SECRET=xxxx
```

プロフィール確認のコードを.envファイルから読み込んで使うように変更します。

```
require('dotenv').load();
var Twitter = require('twitter');

var client = new Twitter({
  consumer_key: process.env.CONSUMER_KEY,
  consumer_secret: process.env.CONSUMER_SECRET,
  access_token_key: process.env.ACCESS_TOKEN_KEY,
  access_token_secret: process.env.ACCESS_TOKEN_SECRET,
});

var params = {screen_name: 'toshihirock'};
client.get('users/show', params, function(error, result, response){
  if (!error) {
    console.log(result);
    console.log(response);
  } else {
    console.log(error);
  }
});
```

これでうまく実行できれば.envファイルとの連携は問題ありません。

また、`.gitignore`を作成し、ドットファイル及びnode_modulesをgit管理から無視するようにします。

```lang:.gitignore
/.*
node_modules/
```

確認します。index.jsのみがgitの管理対象になっていればOKです。


```bash
$git status
On branch master

Initial commit

Untracked files:
  (use "git add <file>..." to include in what will be committed)

	index.js

nothing added to commit but untracked files present (use "git add" to track)
```

# AWS Lambdaからツイートする

次にAWSのLambdaを使ってツイートしてみます。

と言っても少しコードだけを変えるだけではありますが。

```lang:index.js
require('dotenv').load();
var Twitter = require('twitter');

var client = new Twitter({
  consumer_key: process.env.CONSUMER_KEY,
  consumer_secret: process.env.CONSUMER_SECRET,
  access_token_key: process.env.ACCESS_TOKEN_KEY,
  access_token_secret: process.env.ACCESS_TOKEN_SECRET,
});

exports.handler = function(event, context) {
  var params = {status: 'hello from Lambda!'};
  client.post('statuses/update', params, function(error, result, response){
    if (!error) {
      context.succeed(result);
    } else {
      context.fail(error);
    }
  });
}
```

作成できたら、念のためローカル環境からも実行できるようにしてみます。
以下を参考になさせ頂きました。

[AWS Lambdaのための関数のローカル開発とテスト](http://qiita.com/imaifactory/items/092e32b4fc0f69a94061)



```localExec.js
// dirver
var event = {};
var context = {
  invokeid: 'invokeid',
  succeed: function(result) {
    console.log('success');
    console.log(result);
    return;
  },

  fail: function(err) {
    console.log('fail');
    console.log(err);
    return;
  }
}

// exec
var lambda = require('./index');
lambda.handler(event, context);
```

以下でローカルで実行してみます。

```bash
$node localExec.js
```

動作の確認ができたら、lambdaにアップロードします。
事前に **tweet-lambda** というfunctionを作成しておいてください。

```
# ファイル群をzipにまとめる
$zip -r tweet-lambda index.js node_modules .env

# zipファイルのアップロード
$aws lambda update-function-code --function-name tweet-lambda --zip-file fileb://./tweet-lambda.zip
```

マネージメントコンソールなどからテスト実行し、ツイートできていれば成功です!


# YoutubeAPIと連携してTwitter botを作ってみる

普通にツイートしても面白くないのでbotを作ってみます。
今回は、Perfumeの公式MVをランダムでツイートするものを作ってみます。
YoutbeAPIを使ってランダムにPlaylistの情報を取得し、Youtbe用のリンクを作成するものをテストで作ってみます。

以下メモです。

+ YoutubeAPIを使うためにはAPIKEYが必要なので取得しておきます。こちらが参考になりました [NodeでYouTube Search API使ってみた](http://www.tejitak.com/blog/?p=1314)
+ YoutubeAPIはnode-youtubeAPI-simplifierというシンプルなnpmを利用 [Haidy777/node-youtubeAPI-simplifier](https://github.com/Haidy777/node-youtubeAPI-simplifier)


準備ができたら作成します。まずはYoutubeとの連携のみ行うコードを書いて確認してみます。Pefumeというユーザーのプレイリスト一覧を取得し、その中のMVのみのリストからランダムで一つの情報をpick upする形です。

```lang:.env
GOOGLE_API_KEY=xxxx
```

```lang:youtube.js
require('dotenv').load();
var ytapi = require('node-youtubeapi-simplifier');
ytapi.setup(process.env.GOOGLE_API_KEY);

function getRandomInt(min, max) {
  return Math.floor( Math.random() * (max - min + 1) ) + min;
}

// get PerfumeMV randomly
function getRandomPerfumeMV(cb) {
  ytapi.playlistFunctions.getPlaylistsForUser('Perfume').then(function (data) {
    data.forEach(function(playlist) {
      //console.log(playlist.title)
      if(playlist.title === '［Perfume］ MUSIC VIDEOS') {
        //console.log(playlist.playlistId)
        //console.log(playlist.videoCount)
        var num = getRandomInt(0, playlist.videoCount);
        ytapi.playlistFunctions.getVideosForPlaylist(playlist.playlistId).then(function (videos) {
          var str = videos[num].title + '  https://www.youtube.com/watch?v=' + videos[num].videoId;
          console.log(str);
          cb(null, str);
        });
      }
    });
  });
}

// main
getRandomPerfumeMV(function(err, data) {
  console.log(data);
});
```


上記で動作確認ができたらツイートする用に書いたファイルとマージします。（本当は綺麗にファイル分割した方が良いと思いますが、遊びなのでなので。。）

```lang:index.js
// util
require('dotenv').load();

function getRandomInt(min, max) {
  return Math.floor( Math.random() * (max - min + 1) ) + min;
}

// Youtube
var ytapi = require('node-youtubeapi-simplifier');
ytapi.setup(process.env.GOOGLE_API_KEY);

function getRandomPerfumeMV(cb) {
  ytapi.playlistFunctions.getPlaylistsForUser('Perfume').then(function (data) {
    data.forEach(function(playlist) {
      //console.log(playlist.title)
      if(playlist.title === '［Perfume］ MUSIC VIDEOS') {
        //console.log(playlist.playlistId)
        //console.log(playlist.videoCount)
        var num = getRandomInt(0, playlist.videoCount);
        ytapi.playlistFunctions.getVideosForPlaylist(playlist.playlistId).then(function (videos) {
          var str = videos[num].title + '  https://www.youtube.com/watch?v=' + videos[num].videoId;
          console.log(str);
          cb(null, str);
        });
      }
    });
  });
}

// Twitter
var Twitter = require('twitter');

var client = new Twitter({
  consumer_key: process.env.CONSUMER_KEY,
  consumer_secret: process.env.CONSUMER_SECRET,
  access_token_key: process.env.ACCESS_TOKEN_KEY,
  access_token_secret: process.env.ACCESS_TOKEN_SECRET,
});

// main
exports.handler = function(event, context) {
  getRandomPerfumeMV(function(err, data) {
    if (err) context.fail(err);
    else {
      var params = {status: data};
      client.post('statuses/update', params, function(error, result, response){
        if (err) context.fail(err);
        else context.succeed(result);
      });
    }
  });
}
```

また、`.env.js`にはGOOGLE_API_KEYについても追記しておきましょう。

先ほどと同じようにローカル環境でのテストが実行できればアップロードします。

念のため、マネージメントコンソールからもテスト実行を行ってみます。

問題なければ、これを定期実行するようにしてみます。
マネージメントコンソールのlambdaのページでEvent Sourcesのタブを表示し、add event sourceを選択します。
次の画面ではScheduled Eventで１時間おきに実行するように設定してみます。

![Screen Shot 2016-01-11 at 7.14.11 PM.png](https://qiita-image-store.s3.amazonaws.com/0/20193/a93949a1-ec49-9ed8-2bf1-a8f92be6565b.png "Screen Shot 2016-01-11 at 7.14.11 PM.png")


いい感じにできてます！

![Screen Shot 2016-01-12 at 6.50.03 AM.png](https://qiita-image-store.s3.amazonaws.com/0/20193/1b7af0b4-f9f4-23bc-718f-29ed63dbfce7.png "Screen Shot 2016-01-12 at 6.50.03 AM.png")

1時間おきだとうざかったので、1日おきで少し置いておきたいと思います。。