FuelPHPで、Twitter Streaming APIを使う


# 概要
## やりたいこと
Twitter Streaming APIのstatuses/filterを使って、特定のハッシュタグを含み、かつ画像付きのつぶやきを取得してウェブサイトに表示させたい。

## 環境
*	PHP 5.6.16
*	FuelPHP 1.7

# 事前準備
## Twitterのアプリケーション登録

まずは、Twitter側でAPIを使えるようにしないと・・・。

1.	ということで、以下のURLにアクセス

	https://apps.twitter.com/
	
1.	「Create New App」をクリックして、必要事項を入力していきます。
	*	Name：アプリケーション名
	*	Description：アプリケーションの説明
	*	Website：アプリケーションのウェブサイト
	*	Callback URL：コールバック用のURL

	最後に、英語の規約を頑張って読んで、「Yes, I agree」にチェックして、「Create Your Twitter application」をクリック。

1.	アプリケーションが登録されたら、「Key and Access Tokens」タブをクリック。「Consumer Key」と「Consumer Secret」を入手できます。

	「Consumer Secret」はほかの人に知られないよう、厳重に管理しましょう。

1.	続いて、アクセストークンを取得します。同じ画面の下の方にある「Your Access Token」のなかに「Create my access token」というボタンがあるので、クリックしましょう。

	さっきまで何もなかった「Your Access Token」のなかに、「Access Token」と「Access Token Secret」が出現します。

## ライブラリ「Phirehose」の用意
調べてみたなかで、比較的メジャーと思しき「Phirehose」というライブラリを使わせていただきます。

1.	Phirehoseをダウンロード

	https://github.com/fennb/phirehose
	
	ダウンロードしたら、解凍して`phirehose`とかにリネームしたうえで、`/fuel/app/vendor`に放り込みましょう。

2. 今回、`statuses/filter`を使うので、`example`フォルダの中にある、`filter-oauth.php`を、あらかじめphirehose直下に移動しておきます。

# 実装

今回、常時実行プロセスで、つぶやきをひたすら収集していきます。ので、tasksとして実装していきます。

1.	`/fuel/app/tasks/streaming.php`を作成します。

	tasksファイルを作成して、処理を記述します。

	```/fuel/app/tasks/streaming.php
	<?php
	
	// Twitterstreaming
	
	namespace Fuel\Tasks;
	
	// phirehoseを読み込み
	require_once(APPPATH.'vendor/phirehose/lib/Phirehose.php');
	require_once(APPPATH.'vendor/phirehose/lib/OauthPhirehose.php');
	require_once(APPPATH.'vendor/phirehose/filter-oauth.php');
	
	class Twitterstreaming
	{
	
		public static function run()
		{
			\Config::load('twitter', true);
			$api_setting = \Config::get('twitter.api_setting');
			
			define("TWITTER_CONSUMER_KEY", $api_setting['consumer_key']);
			define("TWITTER_CONSUMER_SECRET", $api_setting['consumer_secret']);
			define("OAUTH_TOKEN", $api_setting['access_token']);
			define("OAUTH_SECRET", $api_setting['access_token_secret']);
	
			\Log::debug("Twitterstreaming : ---起動---");
	
			// phirehoseでの処理
			$sc = new \FilterTrackConsumer(OAUTH_TOKEN, OAUTH_SECRET, \Phirehose::METHOD_FILTER);
			$sc->setTrack(array('#ペット'));  // 検索ワード
			$sc->setLang('ja');  // ロケール
			$sc->consume();  // 実行
		}
	}
	
	```
	
	内容としては、ライブラリを読み込んで、認証情報を渡して、実行しているだけ。超シンプル。

1.	`/fuel/app/vendor/phirehose/filter-oauth.php`を作成

	続いてライブラリ側での処理を記述していきます。
	
	
	```/fuel/app/vendor/phirehose/filter-oauth.php
	<?php
	
	class FilterTrackConsumer extends OauthPhirehose
	{
		public function enqueueStatus($status)
		{
			// jsonをデコード
			$tweet = json_decode($status);
	
			// RTではなく、画像付きのツイートのみ
			if (isset($tweet->user->screen_name) && empty($tweet->retweeted_status) && !empty($tweet->extended_entities->media))
			{
				// idは、intだとオーバーフローの恐れがあるので、文字列を使用
				$post_id = $tweet->id_str;

				foreach($tweet->extended_entities->media as $media)
				{
					print $tweet->user->screen_name . ': ' . urldecode($tweet->text) . "\n";
	
					// DBに保存
					$model_sns_post = new \Model_Sns_Post;
					$model_sns_post->user_id		= $tweet->user->screen_name;
					$model_sns_post->username		= $tweet->user->name;
					$model_sns_post->post_id		= $post_id;
					$model_sns_post->posted_date	= date('Y-m-d H:i:s', strtotime($tweet->created_at));
					$model_sns_post->url			= 'https://twitter.com/' . $tweet->user->screen_name . '/status/' . $post_id;
					$model_sns_post->image_url		= $media->media_url_https;
					$model_sns_post->image_w		= $media->sizes->large->w;
					$model_sns_post->image_h		= $media->sizes->large->h;
					$model_sns_post->comment		= trim(str_replace($media->url, '', $tweet->text));
					$model_sns_post->retweet_count	= $tweet->retweet_count;
					$model_sns_post->favorite_count	= $tweet->favorite_count;
					$model_sns_post->save();
				}
			}
		}
	}
	```
	
	ちなみに、当初`json_decode($status, true)`としていましたが、「なぜtrue？」とのご指摘をいただきました・・・。たしかに、上記では配列にする必要はなさそうなので、修正しました。ありがとうございます！しかし、なぜtrueにしたんだ・・・。

1.	実行してみる。

	twitterstreamingを実行します。

	```
	php oil refine twitterstreaming
	```
	データベースに、続々とつぶやきが蓄積されています・・・。

	もし、常時実行させる場合は、以下のような感じでしょうか。

	```
	nohup php oil refine twitterstreaming > /dev/null 2>&1 &
	```

### 以上

超簡単でした・・・。

# 注意すべき点

## 1ツイートに複数画像が掲載されていた場合の考慮

画像情報は、`$tweet['entities']['media']`と`$tweet['extended_entities']['media']`の2種類あるが、`entities`からは1画像分しか取得できない。ので、`extended_entities`から取得するようにすること。

ちなみに、Twitter API 1.1 の`search/tweets`では、`entities`は取得できますが、`extended_entities`は取得できません。

そう、1枚分の画像しか取得できません、なんと・・・。

## Streamg API statuses/filterでの、キーワード検索の不思議な挙動

今回、「#ペット」というハッシュタグで検索しています。なので、以下のようなツイートは、当然収集できます。

```
我が家自慢のペットです！ #ペット
```

これをたとえば「ペット」というキーワードにした場合、前述のツイートは取得できましたが、以下のようなツイートは取得できませんでした。

```
我が家自慢のペットです！
```

対象ワードの前後で文章が続いている場合、取得できませんでした。なんということでしょう・・・。ハッシュタグでの検索には有効かもしれませんが、単純なキーワード検索ということであれば、Twitter API 1.1 の`search/tweets`を定期的に実行する方が良さそうです。

## MySQLのエンコードがUTF-8だと、iOSの絵文字を含むツイートでINSERTエラー

iOSの絵文字をUTF-8で表すと、多くが4バイトです。そのため、文字コードがUTF-8のカラムにINSEERTしようとすると、華麗にエラーがでます。

```
Uncaught exception Fuel\Core\Database_Exception: SQLSTATE[HY000]: General error: 1366 Incorrect string value: '\xF0\x9F\x92\x96\xE2\x9C...' for column 'comment' at row 1 with query: "INSERT INTO `twitter` (`tweet`, `favorite_count`, `share_count, `updated_at`, `created_at`) VALUES ('#絵文字\n💖✨⭐️😘❤️💩😈💕😭😭😭💕💖', 0, 0, '2016-04-26 10:33:46', '2016-04-26 10:33:46')"
```

テーブルやカラムのエンコードを、utf8mb4にすることで、登録できるようになります。
ただし、表示はまた別の問題・・・。