【Laravel 9 × TwitterOAuth】Twitter API v2 searchで取得したツイートをHTMLタグ化して表示

※`oEmbed API`を利用する方法は追って更新します。

こんにちは、現在エンジニアを目指して勉強中の一般男性です。

Twitter API触ってみようと思って
キーワード検索でツイートを取得してHTMLとして表示するところまで
なんとか実装できたので、メモを残しておきます。

自分と同じ初学者の方向けに参考になると幸いです。
また、先輩エンジニアの皆さん、こうすればもっと良くなる
ということがあれば、ぜひコメントいただけると嬉しいです！

## 前提
今回は、TwitterAPIからツイートを取得して、HTMLとして表示するまでを目指します。

ツイートを埋め込むHTMLタグを用意するためには、
対象のツイートのURL情報が必要です。

まずは、個別のツイートのURLがどのように構成されているかを確認しましょう。
```html
https://twitter.com/ユーザーネーム/status/ツイートID
<!-- ユーザーネームは @username の username にあたる部分 -->
```
ということで、検索の部分ではツイートIDとユーザーネームの取得を目指します。

## 環境
・macOS Monterey 12.6
・PHP 8.1.13
・Laravel 9.3.12
・Docker 20.10.2
・Laravel Sail

・composer 2.4.4
・abraham/twitteroauth 5.0.0
・Twitter API v2

## Twitter APIキーの申請とTwitterOAuthのインストール

この投稿では扱いませんので、[こちらの記事などをご参考ください。](https://qiita.com/namizatork/items/439c0604ca15c1ef00cd
)

ちなみに、__Twitter APIの申請には数日~長いと1,2週間__ という
情報をちらほら見かけましたが、自分の場合は一瞬で通りました。(2023/1 時点)

申請と並行して、composer経由でTwitterOAuthを入れておきましょう。
```terminal
composer require abraham/twitteroauth
```

## Search Tweets
### キーワード検索
#### .envとconfigの設定

ますは取得したキーを`.env`にセットします。
```.env
TWITTER_API_KEY = yourKey
TWITTER_API_SECRET = yourSecretKey
TWITTER_BEARER_TOKEN = yourBearerToken
```

次に`config/twitter.php`経由で`.env`の値を使えるように設定
```config/twitter.php
<?php

// 第二引数は.envから値を引っ張ってこれなかった時に使われる値
return [
    'api_key' => env('TWITTER_API_KEY', ''),
    'secret_key' => env('TWITTER_API_SECRET', ''),
    'bearer_token' => env('TWITTER_BEARER_TOKEN', ''),
];
```

両ファイルが正しくセットされているのに値が使えない場合には、
キャッシュを疑ってみるといいかもしれません。

[キャッシュクリアのコマンド貼っておきます。](https://qiita.com/Ping/items/10ada8d069e13d729701)

#### ルーティング設定
こちらも忘れずに。
今回は`/tweet`にアクセスするとツイート取得が始まるようにしておきます。

```routes/web.php
Route::get('/tweet', [App\Http\Controllers\TwitterController::class, 'getTweet']);
```

#### コントローラー作成

私はサービスプロバイダーなどの構成を理解しきれていない部分もあり、
今回はコントローラーに処理を書きました。

```TwitterController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

// これの記載は不要。私と同じ初学者の方はご注意
// require "vendor/autoload.php";

use Abraham\TwitterOAuth\TwitterOAuth;

class Twittercontroller extends Controller
{
  public function getTweet() {

    $connect = new TwitterOAuth(
                        //config.twitterの'api_key'。中身はenv('TWITTER_API_KEY')
                        config('twitter.api_key'),
                        config('twitter.secret_key'),
                        null,
                        config('twitter.bearer_token')
                      );

    // Twitter APIの v2を指定
    $connect->setApiVersion('2');

    // リツイートを除く「キーワード」を含むツイートを検索
    $query = 'キーワード -is:retweet';

    $params = [
       'query' => $query,
       // 他にも取得件数やいつ以降のツイートに絞るなど、複数の設定が可能
     ];

    $result = $connect->get('tweets/search/recent', $params);
    dd($result);
  }
}
```

これでキーワード検索が可能になります。
最低限の内容になっていますので、
より詳しく知りたい方は以下の公式ドキュメント（英語）を参照ください。

[検索クエリ（キーワード等）の設定について](https://developer.twitter.com/en/docs/twitter-api/tweets/search/integrate/build-a-query)
[検索パラメータの設定について](https://developer.twitter.com/en/docs/twitter-api/tweets/search/api-reference/get-tweets-search-recent#tab0)


#### 取得結果
取得できるデータも見ておきましょう。
`edit_history_tweet_ids`、`id`、`text`がワンセットです。
アカウント名なども、パラメータで設定すれば取得できます。

```
{#355 ▼ // app/Http/Controllers/TwitterController.php:29
  +"data": array:10 [▶]
  +"meta": {#356 ▶}
}
```

ここでは２件のツイートしか表示しませんが、
取得するデータ件数はデフォルトでは10件です。
```
{#355 ▼ // app/Http/Controllers/TwitterController.php:29
  +"data": array:10 [▼
    0 => {#330 ▼
      +"edit_history_tweet_ids": array:1 [▼
        0 => "1615584040480043008"
      ]
      +"id": "1615584040480043008"
      +"text": "いまどきの女の子200人への調査結果！ 1位になったキーワードは「一緒にいるとほっとする」で155人。2位は「かわいいね」（108人）、3位は「話しやすい」（58人）という結果になった。参考になればRT"
    }
    1 => {#346 ▼
      +"edit_history_tweet_ids": array:1 [▼
        0 => "1615584027326529537"
      ]
      +"id": "1615584027326529537"
      +"text": """
        ・力を合わせてレオンとジルーを封じる
        ナポリ戦のレベルに戻るには？ここでインザーギの2つ目のキーワードであるチームが登場する。「チームメイトのために皆がもっと走らなければならない。絶対的な価値を持つ攻撃者と対峙するのだから、チームワークを発揮しなければならない。守備は皆次第だ。」 https://t.co/RAu16KGPpB
        """
    }
  ]
  +"meta": {#356 ▼
    +"newest_id": "1615584040480043008"
    +"oldest_id": "1615583203518996480"
    +"result_count": 10
    +"next_token": "b26v89c19zqg8o3fqk41gdjqoqm3frmpfztnb2fzziph9"
  }
}
```

### ユーザー情報の取得

前述のツイート検索でツイートidは取得できました。
追加で、ユーザーネームを取得できるよう
パラメータに変更を加えていきます。

#### expansionsとuser.fieldsパラメータの追加

 `TwitterController`を修正します。
```TwitterController.php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Abraham\TwitterOAuth\TwitterOAuth;

class Twittercontroller extends Controller
{
  public function getTweet() {

    $connect = new TwitterOAuth(
                        config('twitter.api_key'),
                        config('twitter.secret_key'),
                        null,
                        config('twitter.bearer_token')
                      );

    $connect->setApiVersion('2');
    $query = 'キーワード -is:retweet';
    $params = [
       "query" => $query,

       // 以下を追加
       "expansions" => "author_id",
       // ここは書いても書かなくても良い
       "user.fields" => "",
     ];

    $result = $connect->get('tweets/search/recent', $params);
```
ユーザー情報を取得する際には`user.fields`を追加します。
ただ、`user.fields`パラメータは単体では機能しません。

パラメータ`expansions`に`author_id`を指定することで、
idに紐づいたユーザー情報を取得することができるようになるので
忘れずに指定します。

以下公式の抜粋です。
（記載の通り、`author_id`以外にも紐付けられるものがあります）
>You must also pass one of the user expansions to return the desired user fields:
expansions=author_id
expansions=entities.mentions.username
expansions=in_reply_to_user_id
expansions=referenced_tweets.id.author_id

また、`user.fields`はデフォルトで`id`、`name`、`username`を返すので、
`user.fields`には何も指定していません。
他のデータ（プロフ文など）が欲しい方は指定すればOKです。

#### 取得結果
ツイートに付随するユーザー情報は、一見含まれていないように見えます。
が、`includes`に含まれてます。
`laravel`の`dd`では確認できませんでしたが
`print_r`してあげたら出てきました。。

https://qiita.com/aki4003/items/5bc6b97ef496bd094edd

取得したデータは以下のような構造（サンプルです。`dd`では見えません。）
```
// $resultの中身

{#355 ▼ // app/Http/Controllers/TwitterController.php:29
  +"data": array:10 [▶]
  +"includes": [users] => array
  +"meta": {#356 ▶}
}
```
```
// $result->includes->users[0]の値

stdClass Object
(
    [id] => id
    [name] => 名前
    [username] => @username の username
)
```

これで、ツイートのURL生成に必要な
ツイートid、ユーザーネームが取得できました。

## ツイートのHTMLタグ化
### HTMLタグを取得する2つの方法

さて、データを取得できたので、HTML化していくのですが、
そもそも方法としては２通りあります。

・HTMLテンプレを使う
・`TwitterAPI`の`oEmbed API`を使う（更新中）

![スクリーンショット 2023-01-18 19.06.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2535390/f81467b1-e7ac-bb6c-11b3-14fb5ecee492.png)
ということで進めていきましょう。
### HTMLタグテンプレの利用
以下のHTMLを埋め込むことで、Twitter投稿を表示できます。
```html
<blockquote class="twitter-tweet">
   <a href="（埋め込みたいツイートのURL）"></a>
</blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
```


#### URL生成
Search APIで取得したデータを`foreach`で回し、URLを生成します。
for文も試したのですが、たまにキーが存在しないとエラーになることがあり、
下記のforeachが確実でした。
```TwitterController.php
    $result = $connect->get('tweets/search/recent', $params);

    // ここからURL生成
    $base_url = 'https://twitter.com/';

    foreach ($result->data as $data) {
      foreach ($result->includes->users as $user) {
        if ($data->author_id === $user->id) {
          $urls[] = $base_url . $user->username . '/status/'. $data->id;
        }
      }
    }
```

#### テンプレに埋め込む
生成したhtmlを変数に格納してビューに渡せば、表示することができます。
```twitter.html
<!-- laravelプロジェクトなので、本当はtwitter.blade.php -->
@foreach($urls as $url)
  <blockquote class="twitter-tweet">
     <a href="{{ $url }}"></a>
  </blockquote>
  <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
@endforeach
```
ただこの方法、１０件表示するだけでもめちゃくちゃ重かったのでご注意ください、、

### oEmbed API

このAPIを利用するためには、APIのプラン？を
デフォルトの`essential`から`elevated`へアップデートする
申請をしなければいけないようです。

`elevated`の説明は以下
> Higher levels of access to the Twitter API for free with an approved application.

Developer Portalから申請ができますが、
このタイミングで色々な入力項目が発生します
>How will you use the Twitter API or Twitter Data?

ここから先は、申請が通ったタイミングで記載しようと思います。
今回はここまで。

## 参考
### 公式
https://developer.twitter.com/ja

__Search↓__

https://developer.twitter.com/en/docs/twitter-api/tweets/search/introduction

https://developer.twitter.com/en/docs/twitter-api/tweets/search/api-reference/get-tweets-search-recent

__oEmbed↓__

https://developer.twitter.com/en/docs/twitter-for-websites/oembed-api

https://developer.twitter.com/en/docs/twitter-api/v1/tweets/post-and-engage/api-reference/get-statuses-oembed

__Twitter Publish↓__

https://publish.twitter.com/

__TwitterOAuth↓__

https://twitteroauth.com/

### その他

https://qiita.com/namizatork/items/439c0604ca15c1ef00cd

https://qiita.com/ma7ma7pipipi/items/e184abfb51f3381b2873

（こちらはだいぶ古いです）

http://moblog.absgexp.net/oembed/
## おまけ
実際に情報を集めてみてわかったんですが
自分が求めていた情報（観光地情報）は
Twitterで集めるには微妙だった、、

かといって、Instagram APIでの投稿取得も
ハッシュタグに紐づいているみたいで、ちょっと不便そう。

ということで、イメージしていた機能は一旦ペンディングにしました。笑
最初のリサーチはちゃんとやっておかないとですね★

.

にしても、Twitter APIは公式ドキュメントが英語、
かつv1 とv2 の情報が錯綜していたりと
GoogleMaps APIに比べて苦労した感覚があります。

落ち着いたら英語の勉強もしたいな。。