Twitter API：動かなくなったTwitter botを復活させる

# はじめに
Twitter APIを使用してTwitter botを作成していたのですが、APIの仕様変更により、2023年6月頃のツイートを最後に実行されなくなってしまいました。
bot復活までの対処についてまとめます。

botは次のような仕組みで実装していました。
1. GAS（Google Apps Script）をトリガーにPHPのスクリプトをキック
1. PHPからTwitterOAuthというライブラリを使用してTwitter APIを操作
1. ツイートのポストやファボ、ツイートを取得する

サーバー環境は次のとおりです。
* 開発環境：VirtualBoxのCentOS7.5（Vagrant：bento/centos-7.5）
* 本番環境：[さくらのレンタルサーバ - ライトプラン](https://rs.sakura.ad.jp/lite.html)

改修前のアプリのバージョンです。
* PHP：7系
* TwitterOAuth：0.7.2（おそらく）
* Twitter API：v1.1

改修後のアプリのバージョンです。
* PHP：8系
* TwitterOAuth：6.6.6
* Twitter API：v1.1とv2の併用

2017年に作成したきりほとんど改修もしていなかったためTwitter API以外の改修範囲も多く、また、開発環境と本番環境で手順が異なり戸惑ったりと、bot復活までにかなり苦労しました。
結局はTwitter APIの無料プランでは使用できるエンドポイント（Twitter APIにアクセスするためのアドレス）がかなり限定されており、完全に復活させることはできませんでした。

# おおまかな手順
bot復活までのおおまかな手順です。

1. [Twitter APIのバージョン変更](#twitter-apiのバージョン変更)
1. [プロジェクトとアプリの設定](#プロジェクトとアプリの設定)
1. [PHPのライブラリ「TwitterOAuth」の最新化](#phpのライブラリtwitteroauthの最新化)
	* [Composerのインストール](#composerのインストール)
	* [TwitterOAuthのインストール](#twitteroauthのインストール)
	* [PHPライブラリ読み込みパスの変更](#phpライブラリ読み込みパスの変更)
1. [PHPのバージョンを8に変更](#phpのバージョンを8に変更)
1. [PHPのスクリプトの改修](#phpのスクリプトの改修)



# 原因の究明
## ログの確認
スクリプト実行時のログを確認したところ、次のメッセージが出ていました。

> object(stdClass)#17 (1) { ["errors"]=> array(1) { [0]=> object(stdClass)#11 (2) { ["code"]=> int(32) ["message"]=> string(27) "Could not authenticate you." } } } 

「あなたを認証できませんでした」とのことです。

## 認証情報の確認
Twitterのデベロッパーサイトで認証情報を確認しました。

https://developer.twitter.com/en/portal/products

![Twitter APIのバージョンアップ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/46792ac5-f9e3-0352-108c-5bfd4511900e.png)

> The v1.1 APIs have been deprecated and your app is no longer able to access those v1.1 APIs. Please create a new application using one of our v2 Free, Basic or Pro plans today!

「v1.1のAPIは廃止されアクセスできなくなりました。今すぐv2の無料プラン、ベーシックプラン、プロプランのいずれかを使用して新しいアプリケーションを作成してください！」とのことです。

# 対処
## Twitter APIのバージョン変更
まずはTwitter APIのバージョンをv1.1からv2に変更します。

1. [Twitter Developers](https://developer.twitter.com/en/portal/products)のTwitter API v2のページから無料プランへのダウングレードのボタンをクリックする
	![Downgrade](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/6acb9868-cd26-bd8c-7d0c-be4e706f72bd.png)

1. バージョンが変更される
	![successfully](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/67d77192-7ae4-c722-2fd1-2e24100637f8.png)


バージョン変更後、スクリプトを実行したところ、エラーログが出ました。

> object(stdClass)#8 (1) { ["errors"]=> array(1) { [0]=> object(stdClass)#3 (2) { ["message"]=> string(272) "You currently have access to a subset of Twitter API v2 endpoints and limited v1.1 endpoints (e.g. media post, oauth) only. If you need access to this endpoint, you may need a different access level. You can learn more here: https://developer.twitter.com/en/portal/product" ["code"]=> int(453) } } } 

「Twitter APIのエンドポイントへアクセスするには、別のアクセスレベルが必要です。詳しくはこちらをご覧ください」とのことです。

詳しくをこちらのURLへアクセスし、v2エンドポイントの詳細を確認します。

![v2 endpoints](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/4500524c-46d6-e27b-25ab-d3e4060b3fde.png)

![Migrate](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/e1da6035-96c8-e5ad-e733-5496b1db3895.png)

https://developer.twitter.com/en/docs/twitter-api/migrate/ready-to-migrate

> In order to use v2 endpoints, you will need the following things:
> 
> A developer account
> A developer App created within a Project
> Keys and tokens from that Project’s developer App

「v2エンドポイントを使用するには、次のものが必要」とのことです。

* 開発者アカウント
* プロジェクト内に作成された開発者アプリ
* プロジェクトの開発者アプリのキーとトークン

> If you would like to start using v2 endpoints, you will need to attach an App to a Project and use the credentials from that App when making requests to v2 endpoints. 

また、「v2エンドポイントの使用を開始する場合は、Appをプロジェクトにアタッチし、v2エンドポイントへのリクエスト時にそのAppの認証情報を使用する必要があります」とのことです。

## プロジェクトとアプリの設定
Twitter API v2を利用するにはプロジェクトが必要とのことなので、プロジェクトを作成し、アプリをひもづけます。

1. [Twitter Developers](https://developer.twitter.com/en/portal/dashboard)のDashboardページから「Create Project」をクリックする
	![Create Project](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/70676aa4-7802-7003-3592-abc0b6aec77a.png)

1. プロジェクト名を入力する
	![Name Your Project](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/883f7ca5-d49a-0333-b570-817614cd270c.png)

1. 使用用途は「ボット作成」を選択する
	![Which best describes you?](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/20f6fba1-4c51-7192-32a8-c752ac3c0964.png)

1. プロジェクトの説明を記入する
	![Describe your new Project](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/3f5198c5-6274-1877-32f9-130104a0ff12.png)

1. 「既存のアプリを追加」を選択する
	![Add an existing App](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/fae60dac-a2d7-9141-bdc1-f08db584fa77.png)

1. ひもづけるアプリを選択する
	![Add your App](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/aafd9216-d2be-299e-7e37-5a4529174f05.png)

1. ひもづけ完了
	![Confirmation](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/71425e83-3db5-cbef-7eca-84c8d8ccb2db.png)

## PHPのライブラリ「TwitterOAuth」の最新化
Twitter APIを操作するためのライブラリ「TwitterOAuth」がTwitter APIのv1.1にしか対応していなさそうだったので、最新版をインストールします。

インストールには、PHPのパッケージライブラリ管理ツールである「Composer」が必要です。
公式サイトの[Introduction - Composer](https://getcomposer.org/doc/00-intro.md#locally)のローカルインストールの手順にしたがってまずは「Composer」をインストールします。

### Composerのインストール
1. [Composer](https://getcomposer.org/download/)のサイトから、phpではじまる4行のスクリプトをコピーし、インストールしたいパスでコマンドを実行する

	```console
	php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
	php -r "if (hash_file('sha384', 'composer-setup.php') === 'e21205b207c3ff031906575712edab6f13eb0b361f2085f1f1237b7126d785e826a450292b6cfd1d64d92e6563bbde02') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
	php composer-setup.php
	php -r "unlink('composer-setup.php');"
	```
	
	※サイトに記載されているスクリプトは更新されるようなので、サイトから最新のスクリプトをコピーしてください

コマンドを実行した階層に「composer.phar」が作成されていればインストールは完了です。

### TwitterOAuthのインストール
「Composer」と同じ階層に「TwitterOAuth」をインストールします。

1. カレントディレクトリはそのままで、次のコマンドを実行します。

	```console
	php composer.phar require abraham/twitteroauth
	```

1. もし「The zip extension and unzip/7z commands are both missing, skipping.」エラーが出た場合は、zipコマンドをインストールする

	```console
	sudo yum install php-zip
	sudo yum install -y zip unzip
	```

1. Apacheを再起動して、再度twitteroauthをインストールする

	```console
	sudo service httpd restart
	php composer.phar require abraham/twitteroauth
	```

「composer.json」「composer.lock」ファイルと、「vendor」フォルダーの中にabraham/twitteroauthのファイル群が作成されていればインストールは完了です。

### PHPライブラリ読み込みパスの変更
指定したパスに`vendor\abraham\twitteroauth`フォルダーが作成されました。
既存のフォルダー構成と異なってしまったので、パスを指定している箇所を変更します。

* 変更前
	
	```php
	require "twitteroauth/autoload.php";
	require "twitteroauth/src/TwitterOAuth.php";
	use Abraham\TwitterOAuth\TwitterOAuth;
	```

* 変更後

	```php
	require "vendor/autoload.php";
	use Abraham\TwitterOAuth\TwitterOAuth;
	```

「TwitterOAuth.php」は読み込まなくてもよくなったようです。
~~自分はこれを読み込んでいたせいで、なぜか開発環境では実行できるのに本番環境では500エラーになる現象がおき、3日間理由がわからず苦しみました…。~~

変更後、スクリプトを実行したところ、エラーログが出ました。

> Composer detected issues in your platform:
> 
> Your Composer dependencies require a PHP version ">= 8.0.0". You are running 7.1.33.

どうやら最新の「TwitterOAuth」はPHP8にしか対応していないそうです。
調べるとPHP7のサポートは終了しているようで、PHP8を利用したほうがよさそうでした。

## PHPのバージョンを8に変更
### 開発環境
Vagrantで構築した仮想環境のCentOS7.5にPHP8をインストールします。

1. 現在のPHPのバージョンを確認する

	```console
	php -v
	```

	実行結果
	
	```console
	PHP 7.1.33 (cli) (built: Nov 15 2021 10:04:41) ( NTS )
	Copyright (c) 1997-2018 The PHP Group
	Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies
		with Xdebug v2.9.8, Copyright (c) 2002-2020, by Derick Rethans
	```

1. インストール済みのPHP関連パッケージを確認する（念のためメモしておく）
	```console
	yum list installed php*
	```

	実行結果

	```console
	Loaded plugins: fastestmirror
	Repodata is over 2 weeks old. Install yum-cron? Or run: yum makecache fast
	Loading mirror speeds from cached hostfile
	* base: ftp.riken.jp
	* epel: ftp.riken.jp
	* extras: ftp.riken.jp
	* remi-safe: ftp.riken.jp
	* updates: ftp.riken.jp
	Installed Packages
	php.x86_64                                     7.1.33-19.el7.remi                            @remi-php71
	php-cli.x86_64                                 7.1.33-19.el7.remi                            @remi-php71
	php-common.x86_64                              7.1.33-19.el7.remi                            @remi-php71
	php-devel.x86_64                               7.1.33-19.el7.remi                            @remi-php71
	php-gd.x86_64                                  7.1.33-19.el7.remi                            @remi-php71
	php-json.x86_64                                7.1.33-19.el7.remi                            @remi-php71
	php-mbstring.x86_64                            7.1.33-19.el7.remi                            @remi-php71
	php-pdo.x86_64                                 7.1.33-19.el7.remi                            @remi-php71
	php-pecl-xdebug.x86_64                         2.9.8-1.el7.remi.7.1                          @remi-php71
	php-xml.x86_64                                 7.1.33-19.el7.remi                            @remi-php71
	```

1. PHPを削除する

	```console
	sudo yum remove "php*"
	```

1. epel-releaseのアップデートをする

	```console
	sudo yum update epel-release
	```

1. remiのリポジトリを検索し、remi-php81.repoがあるか確認する

	```console
	ll /etc/yum.repos.d/ | grep remi-
	```

	ない場合は、次のコマンドでリポジトリをインストールする

	```console
	yum install http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
	```

1. PHP8.1をインストールする

	```console
	sudo yum -y install --enablerepo=remi,remi-php81 php php-cli php-common php-devel php-mbstring php-pdo php-gd php-xml php-mcrypts php-pecl-xdebug
	```

1. PHPのバージョンを確認し、PHP8.1になっていれば完了

	```console
	php -v
	```

1. Apacheを再起動する

	```console
	sudo service httpd restart
	```

### 本番環境
本番環境である「さくらのレンタルサーバ」のPHPのバージョンを変更します。
こちらの手順を参考にPHP8.1.20へ変更しました。
[PHPのバージョンを変更したい | さくらのサポート情報](https://help.sakura.ad.jp/rs/2241/)

![PHPのバージョン](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/d10b2be3-5b2a-335c-bfa4-7a1cdb0a1578.png)

## PHPのスクリプトの改修
PHPのバージョンを8.1にしてもスクリプトは実行されず、調べるとTwitter API v2になったことにより使えなくなったエンドポイントあるようです。
PHPのスクリプトをTwitter API v2用に書き換える必要がありました。

エンドポイントの移行表はこちらにまとめられています。
[Twitter API endpoint map | Docs | Twitter Developer Platform](https://developer.twitter.com/en/docs/twitter-api/migrate/twitter-api-endpoint-map)

ざっくり次のようにPHPを書き換えました。

### 文字のみのツイート
* 変更前

	```php
	//引数で渡したメッセージを呟く
	function tweet($message){
		// ツイッターにアクセスする
		$connection = new TwitterOAuth(
			CONSUMER_KEY,
			CONSUMER_SECRET,
			ACCESS_TOKEN,
			ACCESS_TOKEN_SECRET
		);

		//つぶやく
		$result = $connection->post("statuses/update", array("status" => $message));

		//返ってきた内容を確認してみる
		return var_dump($result);
	}
	```

* 変更後

	```php
	//引数で渡したメッセージを呟く
	function tweet($message){
		// ツイッターにアクセスする
		$connection = new TwitterOAuth(
			CONSUMER_KEY,
			CONSUMER_SECRET,
			ACCESS_TOKEN,
			ACCESS_TOKEN_SECRET
		);
		//v2の動作にする
		$connection->setApiVersion('2');

		//つぶやく
		$result = $connection->post("tweets", ["text" => $message], true);

		//返ってきた内容を確認してみる
		return var_dump($result);
	}
	```

v1.1ではpostはできなくなってしまったようです。
なので、明示的にAPIのバージョン`2`を指定してあげて、postをv2の書き方に変えてあげる必要がありました。

### 画像付きのツイート
* 変更前

	```php
	//引数で渡した画像とメッセージを呟く
	function tweetMedia($message, $mediaURL){
		// ツイッターにアクセスする
		$connection = new TwitterOAuth(
			CONSUMER_KEY,
			CONSUMER_SECRET,
			ACCESS_TOKEN,
			ACCESS_TOKEN_SECRET
		);

		//画像をアップロードし、画像のIDを取得する
		$imageId = $connection->upload("media/upload", array("media" => "$mediaURL"));

		//つぶやく
		$result = $connection->post("statuses/update", array("status" => $message, 'media_ids' => implode(',', [$imageId->media_id_string])));

		//返ってきた内容を確認してみる
		return var_dump($result);
	}
	```

* 変更後

	```php
	//引数で渡した画像とメッセージを呟く
	function tweetMedia($message, $mediaURL){
		// ツイッターにアクセスする
		$connection = new TwitterOAuth(
			CONSUMER_KEY,
			CONSUMER_SECRET,
			ACCESS_TOKEN,
			ACCESS_TOKEN_SECRET
		);
		//v1.1の動作にする（Twitter API v2は画像のアップロードがまだ未対応のため）
		$connection->setApiVersion('1.1');

		//URLから画像コンテンツを取得する
		$contents = file_get_contents($mediaURL);

		//MIME_TYPEを取得する
		$finfo = finfo_open(FILEINFO_MIME_TYPE);
		$mime_type = finfo_buffer($finfo, $contents);
		finfo_close($finfo);

		//拡張子を設定する
		if(substr($mime_type,0,6) == 'image/'){
			$extension = substr($mime_type,6);
		}else{
			//画像じゃない場合は文字のみツイートする
			tweet($message);
			Exit;
		}

		//画像を保存するパスを指定する
		$mediaPath = dirname(__FILE__) . "/image/TMP_image." . $extension;

		print var_dump($mediaPath);

		//画像をサーバーに保存する（上書き保存）
		file_put_contents($mediaPath, $contents);

		//画像をアップロードし、画像のIDを取得する
		$imageId = $connection->upload("media/upload", ["media" => $mediaPath]);

		//これ以降はv2の動作にする
		$connection->setApiVersion('2');

		$parameters = [
			'text' => $message,
			'media' => [
				'media_ids' => [$imageId->media_id_string],
			]
		];

		//つぶやく
		$result = $connection->post('tweets', $parameters, true);

		//返ってきた内容を確認してみる
		return var_dump($result);
	}
	```

こちらはちょっとややこしいです。
画像のuploadはv2ではまだ未実装のため、upload処理はv1.1で行い、ツイートのpostはv2で行います。

さらにこのスクリプトでは、画像はファイルそのものをuploadするのではなく、Google Driveに格納している画像のURLを指定しています。
昔はURLの指定でuploadできたのですが、最新版ではファイルパスを指定しないとuploadできなくなっていました。
そのため一度サーバーに画像ファイルを保存してからuploadしています。ファイルを保存するときに拡張子を判定しています。

### いいね、リプライなど
こちらについては、Twitter API v2の無料プランでは実行できなくなりました。
無料プランで使用できるエンドポイントは、次の3種類だけです。
* POST /2/tweets（ツイートのポスト）
* DELETE /2/tweets/:id（ツイートの削除）
* GET /2/users/me（自分のユーザー情報の取得）

プランによって使用できるエンドポイントは、[Twitter Developers](https://developer.twitter.com/en/portal/products)のTwitter API v2のページに記載されています。
![Twitter API v2](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/fe489e39-8666-72cb-3b97-728a46f33172.png)

Docsに現在掲載されているアクセスレベル（プラン）についてのドキュメントは古いものも混在していそうです。

こちら「Essential」「Elevated」…といったアクセスレベルは、申し込みページに遷移してもページが存在せず廃止された情報と思われます。
[What's new with Twitter API v2 | Docs | Twitter Developer Platform](https://developer.twitter.com/en/docs/twitter-api/migrate/whats-new)
![old - Access levels](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/486d0234-c19e-627d-4451-9caf824fa56f.png)

おそらく最新の情報はこちらです。
[Twitter API Documentation | Docs | Twitter Developer Platform](https://developer.twitter.com/en/docs/twitter-api)
![New - Access level](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/3b29a16f-fc61-62a6-bd2c-01ce88364a49.png)

情報がいろいろと混在していますが、2023/08/03現在では、Twitter APIのアクセスレベルとエンドポイントについてはこのページの情報が正しそうです。

[Getting Started with the Twitter API | Docs | Twitter Developer Platform](https://developer.twitter.com/en/docs/twitter-api/getting-started/about-twitter-api#v2-access-level)
![Twitter API access levels and versions](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230850/206cf1d7-5e14-fa18-9578-689d6808dab3.png)

このページによると、無料プランで使用できるエンドポイントは次のとおりです。
* Twitter API v2：Tweet作成のみ
* Twitter API v1.1：メディアのuploadなど限定されたもののみ

よって、v2でツイートの検索ができないからといって、かわりにv1.1で行うこともできないようです。

# さいごに
ここ数か月でのTwitter（正しくはXですね…）の動きは目まぐるしく、最新の情報を探し出すのがかなり大変でした。
botを復活させるのに結局5日ほどかかってしまい、めちゃ大変でした。

# 出典
* [Twitter API v1.1が非推奨になり、アクセスできなくなっていたのを解決しました - Qiita](https://qiita.com/nero-15/items/7c24137dd2c2b372501e)