久々の Twitter API、初めての X API

どうも、北千住でもくもく会を開催している Xu です。

[以前の記事](https://qiita.com/Xudev/items/70bc57273846f049570a)でもお話した通り、最近はコミュニティ管理に頭を悩ませている日々を送っております。

いつももくもく会を開催する際に [X](https://x.com/Xu_takax) のほうでも通知を行っていますが、毎回文章を書いてリンクを乗せるがのめんどくさすぎるので、X に改名してから初めての X API にして久しぶりの Twitter API を触ってみようかなと思います。

## ゴール
API を介して自動的にポストが行われる

## 準備
まずは X API を利用申請します。
開発プラットフォームに行き、ログインして Sign up for Free Account リンクをクリックします。
https://developer.x.com/ja
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2466211/d7ef2b80-3833-4c6e-a3dd-d99898389be4.png)

プライバシーポリシーなど色々とチェックして進むと、「Describe all of your use cases of Twitter’s data and API」というテキストエリアが現れ、作文を書いて提出します。

ひと昔は数週間待たされてましたが、今はすぐに審査が終わってダッシュボード画面に移動します。
https://developer.x.com/en/portal/dashboard

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2466211/bdffc688-10b3-4cda-abeb-da8a38640000.png)
左側サイドスライドで作成されたプロジェクトをクリックします。（ここでは「Auto-Post-App」という名前に変更してます。）
URL形式：https://developer.x.com/en/portal/projects/[Your_Prodject_ID]/apps

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2466211/09f05127-e86f-4fa3-8099-e01fba3d7efc.png)
画面下部の Apps パネルに移動して、ギアマークをクリックすると詳細を編集できます。

「User authentication settings」パネルの [ EDIT ] ボタンをクリックすると、Permissions 画面に移動して権限を設定することができます。
例として、今回は主にツイートの書き込み権限が必要なので、「Read and write」まで権限を付与し、Type of APP は Confidentical Client を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2466211/f1c4e115-a27a-4bfd-8c47-98155500290c.png)

下部の App info ではコールバック URL と Website URL が必須項目ですが、適当に記入しても構いません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2466211/eb4e9b5c-9314-443e-b834-d775911dfcc4.png)
なお、コールバック URL は後程必要になるので、記録しておきましょう。

Apps パネルに戻り、鍵マークをクリックするとアクセス情報管理ページに移行します。
URL形式：https://developer.x.com/en/portal/projects/[Your_Prodject_ID]/apps/Your_App_ID/keys

ここでは以下のアクセス情報を入手できます。
Consumer API Key、Consumer API Secret、Client ID、Client Secret、Bearer Token、Access Token、Access Token Secret
必ずバックアップしておきましょう。

さて、アクセス情報も手に入ったことだし、さっそく使ってみましょう！

## Postman でとりあえず POST リクエストを試みる
X API のドキュメントなどはこちらから確認できます。
https://developer.x.com/ja/docs/x-api

まずはツイートを取得してみましょう。
形式は以下になります。
```shell
curl -X GET https://api.twitter.com/2/tweets/{id} -H "Authorization: Bearer <token>"
```
<token> は先ほど保存した Bearer Token です。
{id} は特定のポストの詳細を確認すると URL に表示されてます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2466211/d77efd66-c14d-409d-8c5f-ee9326fb810b.png)
例として今回はこちらの id を使います。
1920119854587421135

```Response.json
{
  "data": {
    "edit_history_tweet_ids": ["1920119854587421135"],
    "text": "API testing",
    "id": "1920119854587421135"
  }
}
```

GET リクエストは検証できた、さて POST リクエストでちゃちゃっと Tweet 投稿をやってみよう。
```Bash
curl -X POST https://api.twitter.com/2/tweets \
    -H "Authorization: Bearer <token>" \
    -H "Content-type: application/json" \
    -d "{\"text\": \"API testing\"}"
```

Response
```Response.json
{
  "title": "Unsupported Authentication",
  "detail": "Authenticating with OAuth 2.0 Application-Only is forbidden for this endpoint.  Supported authentication types are [OAuth 1.0a User Context, OAuth 2.0 User Context].",
  "type": "https://api.twitter.com/2/problems/unsupported-authentication",
  "status": 403
}
```

だめでした！！！！？？？？

## OAuth2.0 Flow with PKCE
とりあえずエラーメッセージの"Authenticating with OAuth 2.0 Application-Only is forbidden for this endpoint.  Supported authentication types are [OAuth 1.0a User Context, OAuth 2.0 User Context]."について調べてみた結果、以下の 2 記事にたどり着いた。

- [PostmanからX APIを使うことで、OAuth2.0の使い方を理解できたので解説してみます](https://qiita.com/yo16/items/e523806154a543f6633c)
- [【備忘録】Twitter API v2 OAuth2.0 コマンドプロンプトからTwitter を使ってみる](https://qiita.com/YouKnow/items/52b62d5487fa45df110c)

これらから、ツイートをするには OAuth2.0 Flow with PKCE が必要だということに気づきました。

簡単に言うと、callback URL を不正利用されて、認可コードを横取りしアクセストークンを盗まれないためにある認証方式です。


### OAuth2.0 認証必要パラメータ
response_type: `code`
client_id: `{Client_id}`
state: `111`などランダムな数列
scope: 今回のアクセスに必要な権限、今回は `tweet.read`、`tweet.write`、`users.read`、`offline.access`くらいにしておきます
redirect_uri: 上記で設定したコールバック URL
※OAuth2.0 の場合、コールバック URL は以前設定されたものと完全一致する必要があります。
code_challenge: ランダムな文字列
code_challenge_method: `plain` か `S256`、`S256` に設定した場合は code_challenge の文字列の Base64 エンコード済み文字列も用意しなければいけない、今回は手っ取り早く `plain` で検証します。

しかし、ここで問題発生！
WEB 版 Postman で認証方式を OAuth2.0 にして、Grant タイプを認可コード（PKCE 利用）にしてもコールバック URL が設定できない！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2466211/7ed67e19-b375-4d01-bd0d-dd9d15636798.png)
その原因としては、WEB 版はブラウザー経由の認証を想定しているため、変更できない仕様になっています！（筆者は認証方式を変更できるオプションが見つかりませんでした、見つけた方は教えてください！）

泣く泣くパッケージ版をインストールして、コールバック URL を設定できたが、認証時に「Xこのブラウザは現在サポートされていません」とポップアップ画面に表示される。

WEB 版はコールバック URL 設定できない、パッケージ版は認証ボタンが押せない。。。

無限ループって怖くね？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2466211/cc85e0ce-29f8-4721-9853-840690d5a4cb.png)


## 回避策
### 1. 結局直接ブラウザに入力して認証
```
https://twitter.com/i/oauth2/authorize?response_type=code&client_id=
{Client_id}&state=111&scope=tweet.read%20tweet.write%20users.read%20offline.access&
redirect_uri=https%3A%2F%2Ftwitter.com&code_challenge=
{code_challenge}&code_challenge_method=plain
```

認証後、以下のような URL にリダイレクトされます。
https://x.com/home?state=111&code={code}

### 2. トークンを取得
※上記で取得した `{code}` の有効期限は 30 秒なので、期限切れになるまでに API を叩く必要があります。
```shell
curl --location --request POST https://api.x.com/2/oauth2/token \
    --basic -u "{Client_id}:{Client_Secret}" \
    --header "Content-Type: application/x-www-form-urlencoded" \
    --data-urlencode "code={code}" \
    --data-urlencode "grant_type=authorization_code" \
    --data-urlencode "client_id={client_id}" \
    --data-urlencode "redirect_uri=https://twitter.com" \
    --data-urlencode "code_verifier={code_challenge}"
```

```Response.json
{
  "token_type": "bearer",
  "expires_in": 7200,
  "access_token": "{access_token}",
  "scope": "tweet.write users.read tweet.read offline.access",
  "refresh_token": "{refresh_token}"
}
```

ここで大事なのは `{access_token}` と `{refresh_token}` です。
`{access_token}` は後述の手順で使われ、`{refresh_token}` は期限切れの際に便利に再取得に使用できます。

`"expires_in"` に記載された 7200 は有効期限の秒数であるため、このトークンは 2 時間使用できることがわかります。

### 3. API からツイートをポスト
```shell
curl -X POST https://api.twitter.com/2/tweets \
    -H "Authorization: Bearer {access_token}" \
    -H "Content-type: application/json" \
    -d "{\"text\": \"API testing\"}"
```

```response.json
{
  "data": {
    "text": "API testing",
    "edit_history_tweet_ids": ["1920119854587421135"],
    "id": "1920119854587421135"
  }
}
```

これでツイートをポストすることができました。

### 4.トークが期限切れた場合
以下のリクエストでアクセストークンを再取得できます。
```shell
curl -X POST https://api.twitter.com/2/oauth2/token \
--basic -u "{client_id}:{client_secret}" \
--header "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
--data-urlencode "refresh_token={refresh_token}"
```

## あとがき
今回は X API を使ってツイートの投稿を実現できましたが、都度手動で認証しないといけないのが難点です。
コードに実装する際に Selenium などで自動操作することも可能でしょうが、環境依存度が高まるのと、今後 UI が変更された際に対応が遅れてしまう懸念もあるため、ほかに回避策をまた今後考えていこうと思います