自力で X API v2 + OAuth 1.0a で投稿する
自作サイトの更新情報を各SNSに勝手に流すようにしよう、ということでTwitter(現X)にも対応したが、情報が古いものが大量にあるので苦労したこともあり現時点での方法を残す
なお適当なパッケージ等を使っている場合はそのドキュメントを見たほうがいいだろう。今回はOAuthの署名情報(Signature)も自分で作っている
なぜそんな面倒なことをしたのかというと、動作環境がCloudflare Workersでいわゆるエッジサイドなので使用しているNode APIによっては動かないのだ。おまけにポストするだけなので必要なものだけ入れて可能な限りスリムにしたいというのもある
手段
ざっくり以下の要素が必要
- 認証情報を取得する
- エンドポイントを指定する
- Nonceを作成する
- Timestampを作成する
- 署名情報を作成する
- 認証情報を設定する
- 要素をすべてセットにしてエンドポイントを叩く
順番に行ってみよう
認証情報を取得する
デベロッパーポータルでAPIキーとAPIシークレットを取得する
今回は認証にOAuth 1.0aを利用するのでアクセストークンとアクセスシークレットも必要になる
スクリーンショットを取って載せるのは面倒なので文字情報だけで申し訳ないが、軽く説明するとプロジェクトを作成してその下にAPPを作成するという方法を取る(昔のAPIキー等が残っていてもそれは使えないので注意)
無料枠だとAPPは1つしか作れないが、まあ問題ないだろう
作成後、そのままの流れで設定を進めると権限がReadだけの状態になるのでAPPの設定画面でUser authentication settingsの項から設定を変更する必要がある。ここでRead/WriteにしてからKeys and TokensでAccess Token and Secretを選びそれぞれを生成&保存する
エンドポイントの指定
Twitter/X のAPIは v1.1 と v2 の2つがある
この内無料枠のアカウントではv1.1のAPIは使えないので強制的にv2のAPIを使用することになる。今回は投稿に限っているのでAPIのエンドポイントも特定していうが、1.1とは /1.1/statuses/update.json
のエンドポイントのことである。使えるのは /2/tweets
の方である
ここでTwitter/XのAPIドキュメントを読んでいるとOAuth1.0aでv2APIが叩けるのか?と思う人もいるかも知れないが、OAuthのバージョンとTwitter/XのAPIバージョンは別の話である(紛らわしい
というわけでOAuth1.0aの認証でv2のAPIを叩いていく
Nonceを作成する
まずナンスを作成する。一意性が担保できればいいので適当な長さの文字列を作ってあげればいい
const nonce = Math.random().toString(36).substring(2,16)
Timestampを作成する
これもそこまで重要ではない(あまりに現在時刻から離れすぎるとAPI側で弾かれるらしい
const timestamp = Math.floor(Date.now() / 1000).toString()
署名情報を作成する
だいたいは以下のドキュメントのとおりでいいのだが、これはv1.1のAPIでv2のAPIの場合いくつかの部分で変更がある
まずパラメータの中にinclude_entities
がないこと、そして投稿本文(ここではstatus
パラメータ)がないこと
bodyで送信する内部のパラメータはどうすればいいかというと、v2APIの場合はcontent-typeがApplication/jsonなのでパラメータに含まれないのが正解となる
よってパラメータは上記で作成した nonce と timestamp と、さらにはデベロッパーポータルで得た APIキー、アクセストーンをセットして
const oauth_consumer_key = apiKey
const oauth_nonce = nonce
const oauth_signature_method = 'HMAC-SHA1'
const oauth_timestamp = timestamp
const oauth_token = accessToken
const oauth_version = '1.0'
const paramsString = [
`oauth_consumer_key=${encodeURIComponent(oauth_consumer_key)}`,
`oauth_nonce=${encodeURIComponent(oauth_nonce)}`,
`oauth_signature_method=${encodeURIComponent(oauth_signature_method)}`,
`oauth_timestamp=${encodeURIComponent(oauth_timestamp)}`,
`oauth_token=${encodeURIComponent(oauth_token)}`,
`oauth_version=${encodeURIComponent(oauth_version)}`,
].join('&')
となる。これにエンドポイント情報とMethod情報を組み込んで
const baseString = `POST&${encodeURIComponent(endpoint)}&` + encodeURIComponent(paramsString)
となる
あとはこれをキーを使って署名する。鍵もキーと同様にデベロッパーポータルで得たAPIシークレットとアクセストークンシークレットを組み合わせて作成する
ヘッダー情報で付与する際に signature はパーセントエンコーディングされている必要があるのでここで済ませておく
const signingKey = `${encodeURIComponent(apiSecret)}&${encodeURIComponent(accessTokenSecret)}`
const hmac_sha1 = crypto.createHmac('sha1', signingKey)
const signature = encodeURIComponent(hmac_sha1.update(baseString).digest('base64'))
cryptoは node:crypto
パッケージである。これはエッジサイドでも使える互換性が確保されているAPIだ
認証情報を設定する
OAuth認証では
oauth_consumer_key
oauth_nonce
oauth_signature
oauth_signature_method
oauth_timestamp
oauth_token
oauth_version
をそれぞれ入れてつなげた文字列をヘッダーに入れる必要がある。細かい部分はTwitter/Xのドキュメントに書いている通りなのでそっちを読んでもらいたい
要素をすべてセットにしてエンドポイントを叩く
あとはヘッダーを設定して適切なbodyをセットしてPOSTで送信する
const oauthHeader = [
`oauth_consumer_key="${apiKey}"`,
`oauth_nonce="${nonce}"`,
`oauth_signature="${signature}"`,
`oauth_signature_method="HMAC-SHA1"`,
`oauth_timestamp="${timestamp}"`,
`oauth_token="${accessToken}"`,
`oauth_version="1.0"`,
].join(', ')
const body = JSON.stringify({ text: tweet })
const header = new Headers()
headers.set('Authorization', `OAuth ${oauthHeader}`)
headers.set('Content-Type', 'application/json')
headers.set('Content-length', body.length.toString())
headers.set('Host', 'api.x.com')
headers.set('Accept', '*/*')
headers.set('Connection', 'close')
const response = await fetch(endpoint, {
method: 'POST',
headers,
body,
})
これで送信できる はず
まとめ
半日ぐらいAPIのバージョンとOAuthのバージョンとさらにはAPIのプランによる制限がわからなくてめっちゃ迷ってしまったのでここに記す
もしパッケージとかライブラリとか使いたくねぇ!って人は頑張ってほしい。こういっては何だが、今のXというサービスは真面目に信頼できないサービスなのでトラブル時はかなり気を使うことになると思う
Discussion