X (Twitter) API + Oauth 1.0a で画像付き投稿をする
はじめに
画像付きツイートをするには現状(2024.09時点)では v1.1 API を用いて画像をアップロードし、その結果( media_id
)を用いてツイートする必要があります。
そして Twitter の v1.1 API へのリクエストには OAuth 1.0a が必要なため、OAuth 1.0a を用いて認証し画像付きツイートをするまでを丁寧に解説していきます。
v2 API である https://api.twitter.com/2/tweets
も OAuth 1.0a の認証で利用することができるため、OAuth 1.0a の認可を得られれば画像付きツイートをを行うことができます。
最初調べ始めたとき、すぐに実装できるだろうと思っていたのですが情報が分散しているのと、動く状態のサンプルコードがなく実装にかなり苦戦しました。また、OAuth 1.0a 自体の複雑さも相まって、実装にはかなりの時間を要しました。
意外と需要がありそうなのに情報が少ないので、しっかりとまとめていこうと思います。
今回の実装は Python で行いました。トークン周りさえ設定すれば実際に画像付き投稿が可能な状態のコードも GitHub にアップしておいたので合わせて参照してください。なお、理解を促進することを目的としているため、丁寧に構造化はしていません。シンプルに動くコードを目指しています。
ちなみにこの実装が必要になったのは、今開発している VR ゲーム内で生成した画像を X に投稿する機能を実装する必要があったためです。
短くサッと遊べて、おもしろ画像が生成できるミニゲームになっているので、ぜひプレイしてみてください!
全体の流れの把握
OAuth 1.0a を用いた認証・認可および API リクエストはかなり複雑になってくるので、全体の流れを概観しておきます。
- 各種 API へのリクエストに際して、シグネチャの生成およびヘッダの設定が必要
- ユーザのアクセストークンを取得する前の段階で利用するリクエストトークン取得 API などでも同様にシグネチャが必要
- OAuth 1.0a 認証方式を利用しユーザ認可を得てアクセストークンを取得する
- トークンの取得にはリクエストトークンなどのフローを用いてアクセストークンに変換する
- 取得したアクセストークンを用いてメディアアップロード API を用いて画像をアップロードする
- アップロードした画像の
media_id
を用いてツイート API にリクエストする
ものすごくざっくりまとめると、「各種リクエストには複雑なヘッダ設定処理を伴う。そして実際に投稿をするためのアクセストークンおよびアクセストークンシークレットを取得するために、リクエストトークンの取得から開始する」という感じです。
トークン取得の話の前に、まずは一番大事なヘッダの設定とシグネチャの生成について解説していきます。
シグネチャの生成とヘッダの設定
ぶっちゃけ、ここの部分が理解できればほぼすべての API のリクエストはむずかしくないと思います。ということで、しっかりとここの章は把握してください。
今回の実装にあたって、大まかな実装方針については以下の記事を参考にさせていただきました。
上記記事から、シグネチャ生成についての流れの図を引用させていただきます。
この図が説明しているのは、署名に際して キーをどう生成 し 値をどう生成 しているかを示しています。
ものすごくざっくり説明すると「API リクエストに必要なパラメータとメソッド名(POST や GET など)、URL をひとまとめにしたデータを、アプリのキー(Consumer Secret
)とユーザのシークレット( Access Token Secret
)を用いてハッシュ化し、これを署名とする」ということをやっています。
実装していく中で苦労したのは、リクエストヘッダに設定しなければならない値などが API ごとにちょっとずつ違っていたり、利用している署名のキー( Access Token Secret
など)が間違っていたりしてもエラー内容が Unauthorized
のみで返ってくるため、なにが問題なのかの原因特定が困難だったことです。
ヘッダに設定するデータについてはこのあと詳しく見ていきますが、署名のキーとなる部分については以下のことを頭に入れておいてください。
「アプリ開発者の Consumer Secret
と 投稿するユーザの Access Token Secret
が必要である」
では早速生成について見ていきましょう。
リクエストに含める必要のある必須のパラメータ
次に示すのは、どのリクエストにも必ず含めるパラメータのリストです。前述したように、API によっては以下のパラメータリストに追加のパラメータが必要なことがありますが、基本的にどのリクエストでも必ずこれらは必要となります。
【API リクエスト時に必ず含める必要のあるパラメータ】
Key | Value (一部は例) | 説明 |
---|---|---|
oauth_consumer_key | xxxxxxxxxxxx | Twitter の Developer Portal で作成したアプリの Consumer Key
|
oauth_nonce | yyyyyyyyyyyy | リクエストごとに生成する一意の ID |
oauth_signature_method | HMAC-SHA1 | シグネチャメソッドは「HMAC-SHA1」を使用 |
oauth_timestamp | 1724938694 | リクエスト時のタイムスタンプ |
oauth_version | 1.0 | OAuth 1.0a を利用するため「1.0」 |
oauth_signature | zzzzzzzzzzzz | 諸々のデータを利用して作成したシグネチャ(base64 エンコーディング)(後述) |
シグネチャおよびヘッダの生成のためのデータ
さて、ではこれらのデータを使ってどうやってシグネチャを生成するのかを見ていきます。
シグネチャの生成に含めるパラメータは利用する API ごとに異なるため、まずは投稿 API へリクエストすることを前提に解説していきます。(おそらく一番シンプルな API リクエストだと思います)
対象となる API の URL は https://api.twitter.com/2/tweets
です。
この API にリクエストすることを前提に、まずは必要となるすべてのデータをリストアップします。
Key | 説明 |
---|---|
oauth_consumer_key | Twitter の Developer Portal で作成したアプリの Consumer Key
|
oauth_consumer_secret | Twitter の Developer Portal で作成したアプリの Consumer Secret
|
oauth_nonce | リクエストごとに生成する一意の ID |
oauth_signature_method | シグネチャメソッドは「HMAC-SHA1」を使用 |
oauth_timestamp | リクエスト時のタイムスタンプ |
oauth_token | 認可フローにて取得したユーザのアクセストークン |
oauth_token_secret | 認可フローにて取得したユーザのアクセストークンシークレット |
oauth_version | OAuth 1.0a を利用するため「1.0」 |
oauth_signature | 諸々のデータを利用して作成したシグネチャ(base64 エンコーディング) |
endpoint URL | 実際に叩く API の URL |
text | 投稿するテキスト |
長々と説明をする前に、まずは実際のコードを見たほうが理解が早いと思うのでコードを示します。いくつかのポイントについてはコメントに記載しています。
# API リクエストに利用するヘッダを作成する
def create_oauth_header(
endpoint_url,
oauth_consumer_key,
oauth_consumer_secret,
oauth_token_secret,
verbose=False,
**additional_parameters):
# ヘッダに含める必要があるパラメータの設定・生成
method = "POST"
oauth_signature_method = "HMAC-SHA1"
oauth_version = "1.0"
oauth_nonce = generate_nonce()
oauth_timestamp = str(get_timestamp())
oauth_parameters = {
"oauth_consumer_key": oauth_consumer_key,
"oauth_signature_method": oauth_signature_method,
"oauth_timestamp": oauth_timestamp,
"oauth_nonce": oauth_nonce,
"oauth_version": oauth_version,
}
# 追加のパラメータがある場合はここで追加する
all_parameters = oauth_parameters.copy()
all_parameters.update(additional_parameters)
# パラメータは、キーを昇順で並べる必要がある
# パラメータの値は URL エンコーディングが必要
sorted_parameters = "&".join(f"{encode_text(k)}={encode_text(v)}" for k, v in sorted(all_parameters.items()))
# ベースストリングの作成
base_string = f"{method}&{encode_text(endpoint_url)}&{encode_text(sorted_parameters)}"
# Create a signing key
signing_key = f"{encode_text(oauth_consumer_secret)}&{encode_text(oauth_token_secret)}"
# シグネチャのハッシュを計算し、base64 エンコーディング
signature = base64.b64encode(hmac.new(signing_key.encode(), base_string.encode(), hashlib.sha1).digest()).decode()
if verbose:
print(f"Base String: {base_string}")
print("---------------------------------------------")
print(f"Signing Key: {signing_key}")
print("---------------------------------------------")
print(f"Sorted Parameters: {sorted_parameters}")
print("---------------------------------------------")
print(f"Base String: {base_string}")
print("---------------------------------------------")
print(f"Signature: {signature}")
# Authorization ヘッダの作成
all_parameters["oauth_signature"] = signature
# ヘッダは key="value", key="value", ... として並べる
auth_header = "OAuth " + ", ".join([f'{encode_text(k)}="{encode_text(v)}"' for k, v in all_parameters.items()])
return auth_header
データの準備
順に見ていきましょう。
# ヘッダに含める必要があるパラメータの設定・生成
method = "POST"
oauth_signature_method = "HMAC-SHA1"
oauth_version = "1.0"
oauth_nonce = generate_nonce()
oauth_timestamp = str(get_timestamp())
前述の通り、リクエストに必須で含める必要のあるパラメータのうちの 4 つと、API へのメソッド名を定義しています。
上 3 つは固定値なので問題ないですね。
投稿用 API は POST になるので POST
を指定します。oauth_version
は、今回は 1.0 を用いて認証するため 1.0
を指定します。また、署名のハッシュアルゴリズムは HMAC-SHA1
を用いることが Twitter の API 仕様で決められているためこれを指定します。
続くふたつの値について説明します。
oauth_nonce
は「Number used once」の略で、通信の際に利用される一度きりだけ使われる任意の文字列です。基本的に重複しない値なら問題ありません。
最後の oauth_timestamp
は、API を叩くときのタイムスタンプです。これが、サーバの時間とあまりに大きく違う場合はエラーになるようです。
generate_nonce の実装
def generate_nonce(length=32):
return ''.join(random.choices(string.ascii_letters + string.digits, k=length))
timestamp 計算の実装
def get_timestamp():
# 現在の時刻をUTCで取得
now = datetime.datetime.utcnow()
# UNIXタイムスタンプを取得(秒単位)
timestamp = int(time.mktime(now.timetuple()))
return timestamp
続く辞書定義はリクエストヘッダに含める内容をまとめたものです。
oauth_parameters = {
"oauth_consumer_key": oauth_consumer_key,
"oauth_signature_method": oauth_signature_method,
"oauth_timestamp": oauth_timestamp,
"oauth_nonce": oauth_nonce,
"oauth_version": oauth_version,
}
今回の実装では上記の辞書に追加辞書として以下のようにパラメータを追加するようにしています。
# 追加のパラメータがある場合はここで追加する
all_parameters = oauth_parameters.copy()
all_parameters.update(additional_parameters)
データの整形
パラメータの準備が整いました。ここから、このパラメータを用いてシグネチャおよびヘッダを生成していきます。
ここで準備するのは以下の形にパラメータを並べることです。
key=value&key=value&...
# パラメータは、キーを昇順で並べる必要がある
# パラメータの値は URL エンコーディングが必要
sorted_parameters = "&".join(f"{encode_text(k)}={encode_text(v)}" for k, v in sorted(all_parameters.items()))
このコードは辞書に格納されたパラメータを、キーを元に昇順でソートした上で key=value
形式に変換し、&
で連結しています。また value
は RFC 3986 に基づいた URL エンコードする必要があります。この処理は、シグネチャ生成のために必要なデータを整形するための処理です。
シグネチャの生成
情報が揃ったので、いよいよシグネチャを生成していきます。
シグネチャの生成は HMAC-SHA1 アルゴリズムを用います。そしてそのキーとなるのが Consumer Secret
と Access Token Secret
です。また、値の部分(ベースストリング)は上記のパラメータを用いて以下のように生成します。
# ベースストリングの作成
base_string = f"{method}&{encode_text(endpoint_url)}&{encode_text(sorted_parameters)}"
このベースストリングは「リクエストメソッド、エンドポイント URL、パラメータを &
で連結したもの」です。
前述のソート時にも指定していましたが、各データは RFC 3986 に基づくエンコードを行う必要があります。これは特殊文字を %
でエンコードする処理です。( encode_text
メソッドの実行部分)
encode_text の実装
def encode_text(text):
# RFC 3986 に基づく URL エンコード
return urllib.parse.quote(text, safe='')
次に生成しているのはハッシュ化に用いるキーです。これは Consumer Secret
と Access Token Secret
を &
で連結したものです。
# Create a signing key
signing_key = f"{encode_text(oauth_consumer_secret)}&{encode_text(oauth_token_secret)}"
最後に、準備したキーとデータを元にハッシュ化し、さらにそれを base64 エンコードします。
# Create a signature
signature = base64.b64encode(hmac.new(signing_key.encode(), base_string.encode(), hashlib.sha1).digest()).decode()
ヘッダの生成
前段まででシグネチャの生成が終わりました。最後に、それを用いて最終的なヘッダを作成します。
# Authorization ヘッダの作成
all_parameters["oauth_signature"] = signature
# ヘッダは key="value", key="value", ... として並べる
auth_header = "OAuth " + ", ".join([f'{encode_text(k)}="{encode_text(v)}"' for k, v in all_parameters.items()])
作成したシグネチャを oauth_signature
というパラメータ名に設定し、それを含めたヘッダを作成します。ヘッダはすべてのパラメータを key="value"
の形式にしたものを ,
区切りで並べたものになります。
最終的に生成されるヘッダの見た目は以下のようになります。(見やすいように改行を含めていますが、実際には改行は含めません)
OAuth
oauth_consumer_key="xvz1evF4PTGEFPog",
oauth_nonce="6eb24361e7250e5112288fa54dd8f634a732034c4301910c2dc8b3db",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1581159389",
oauth_token="370773112-GmHMAgYyLbNIKZeRFsMPR9EMZ9weJAEb",
oauth_version="1.0",
oauth_signature="8TACi1tsshSi9dfiLa8Vm8SasTs%3D"
簡単にまとめると「ヘッダに含める情報を用いて署名を行った」というわけです。そのため、送信内容と署名に誤りがあると Unauthorized
が返ってくることになります。
実際に投稿してみる
だいぶかかりましたが、リクエストに利用できるヘッダの作成が終わりました。これを用いて実際に投稿する API にリクエストする処理を見てみます。
投稿に必要な Consumer Key
などは dotenv
モジュールを用いて読み込んでいます。
def request(parameters):
endpoint_url = "https://api.twitter.com/2/tweets"
# ここでは、各データを dotenv で読み込んでいる
oauth_consumer_key = os.environ.get("CONSUMER_KEY")
oauth_consumer_secret = os.environ.get("CONSUMER_SECRET")
oauth_token = os.environ.get("AUTH_TOKEN")
oauth_token_secret = os.environ.get("AUTH_TOKEN_SECRET")
# ツイートの API にはユーザの Access Token と Access Token Secret をヘッダに含める必要がある
auth_header = create_oauth_header(
endpoint_url=endpoint_url,
oauth_consumer_key=oauth_consumer_key,
oauth_consumer_secret=oauth_consumer_secret,
oauth_token=oauth_token, # ここで指定しているアクセストークンは、後述する認可フローによって取得したユーザのアクセストークン
oauth_token_secret=oauth_token_secret, # と同様に取得したアクセストークンシークレット
verbose=False)
# リクエストヘッダーのセット
headers = {
"Content-Type": "application/json",
"Authorization": auth_header,
}
print(f"Access the api [{endpoint_url}] ...")
response = requests.post(endpoint_url, headers=headers, json=parameters)
return response
if __name__ == "__main__":
parameters = {
"text": "Hello, Twitter API!",
}
response = request(parameters)
print(response.text)
create_oauth_header
関数は前段で解説したものです。この関数を通してリクエストヘッダを生成します。
ヘッダを設定している部分は以下の通りです。
headers = {
"Content-Type": "application/json",
"Authorization": auth_header,
}
実際にリクエストを送っているのが以下の部分です。
response = requests.post(endpoint_url, headers=headers, json=parameters)
リクエストに付与されている parameters
は以下のように設定した、ポストする内容です。
parameters = {
"text": "Hello, Twitter API!",
}
投稿が成功するとツイート ID などが返却され、もし失敗した場合は Unauthorized
などのエラーが返ってきます。
シグネチャ生成、ヘッダ生成の部分が完成してしまえば、リクエストそのものはシンプルなのが分かってもらえたでしょうか。
さて、一番重要かつ複雑な署名についての説明が終わりました。これが理解できてしまえば他の API リクエストは攻略したも同然です。
改めて押さえておきたいポイントは以下です。
- リクエストに含めるパラメータには必須の内容がある
- 投稿などほとんどの API リクエストにはアクセストークンが必要
- リクエストヘッダはパラメータを元にした署名(シグネチャ)が必要
そしてこれから説明するのは、API リクエストに必要な「ユーザのアクセストークンを取得する方法」となります。それが「認可フロー」です。
認可フローを理解する
ということで、まずは認可フローの全体の流れを概観します。シグネチャ生成とは少し違った複雑さがあるので全体の流れをまずは把握しておきましょう。
-
https://api.twitter.com/oauth/request_token
API へリクエストし、「アクセストークンの交換に必要なリクエストトークン」を取得する- API から返される値が
oauth_token
となっているので混乱するが、これはまだアクセストークンではない
- API から返される値が
- 取得したリクエストトークンを用いて
https://api.twitter.com/oauth/authenticate
へ「ユーザにアクセス」してもらう- ここはユーザ操作による認可になるためブラウザなどで X へアクセスしてもらい、許可ボタンを押してもらう必要がある
- API リクエストではない点に注意
- ユーザが認可してくれた場合(許可ボタンを押下した場合)、事前に設定したコールバック先にブラウザが遷移する
- このコールバック先は任意ではなく、Twitter Developer Portal で設置しておく必要がある。自由に設定できると意図しないページ飛ばされる、という懸念があるため
- 上記理由により Twitter Developer Portal のコールバック URL リストに設定していない URL をリクエストに含めるとエラーになる
- コールバック先の URL に、クエリの形で
oauth_token
とoauth_verifier
というパラメータが付与されて返ってくる- e.g.)
https://example.com/callback?oauth_token=xxxx&oauth_verifier=yyyy
- この
verifier
コードは生存期間が 30 秒ほどらしいので、時間が経つと使えなくなる点に注意
- e.g.)
- この
oauth_token
とoauth_verifier
をリクエストに含めてhttps://api.twitter.com/oauth/access_token
にリクエストを送る- このリクエストが正常に返ってきたら、晴れてユーザのアクセストークンとアクセストークンシークレットが手に入る
文章だけで見てもだいぶ込み入っているのが分かるかと思います。
大雑把にまとめると、
- ユーザの認可に伴う下準備をする
- ユーザに認可してもらう
- 認可してもらったらアクセストークンを取得する
という流れになります。
認可フローの実装
では実際にこれを実装していきます。
ちなみに、ユーザがブラウザを介して許可をする以外の API リクエストすべてに、前述のシグネチャ生成のフローが必要となります。それを念頭に入れてこの先を読み進めてください。
今回実装した内容を以下に示します。
import os
import requests
from utility import create_oauth_header
import dotenv
dotenv.load_dotenv()
# Consumer Key と Consumer Secret は dotenv で読み込む
oauth_consumer_key = os.environ.get("CONSUMER_KEY")
oauth_consumer_secret = os.environ.get("CONSUMER_SECRET")
# リクエストトークン取得のための API とコールバック先の指定
callback_url = "http://localhost:11230/oauth/redirect"
request_endpoint_url = "https://api.twitter.com/oauth/request_token"
# まずはリクエストトークンを取得するための API リクエストのヘッダを作成する
auth_header = create_oauth_header(
endpoint_url=request_endpoint_url,
oauth_consumer_key=oauth_consumer_key,
oauth_consumer_secret=oauth_consumer_secret,
oauth_token_secret="", # まだアクセストークンシークレット取得前なので空文字で OK
oauth_callback=callback_url, # ヘッダにも oauth_callback を含めないとデフォルトのコールバック先に飛ばされてしまう
verbose=False)
req_headers = {
"Authorization": auth_header,
}
# リクエスト内容にコールバック先 URL を含める
request_token_params = {
"oauth_callback": callback_url,
}
response_req = requests.post(request_endpoint_url, headers=req_headers, json=request_token_params)
response_req_text = response_req.text
# 返されたリクエストトークンを取り出す
oauth_token_kvstr = response_req_text.split("&")
token_dict = {x.split("=")[0]: x.split("=")[1] for x in oauth_token_kvstr}
req_oauth_token = token_dict["oauth_token"]
authenticate_url = "https://api.twitter.com/oauth/authenticate"
# コンソールに URL が表示されるのでそこにアクセスし許可ボタンを押す
print("Please access the following URL and get the OAuth Verifier.")
print(f"{authenticate_url}?oauth_token={req_oauth_token}")
# 許可後に遷移したコールバック URL に付与されている verifier を手入力する
oauth_verifier = input("OAuth Verifierを入力してください> ")
access_endpoint_url = "https://api.twitter.com/oauth/access_token"
auth_header = create_oauth_header(
endpoint_url=access_endpoint_url,
oauth_consumer_key=oauth_consumer_key,
oauth_consumer_secret=oauth_consumer_secret,
oauth_token_secret="", # まだアクセストークンシークレットは未取得なので空文字で OK
oauth_token=req_oauth_token, # リクエストトークン
oauth_verifier=oauth_verifier, # 手入力した verifier
verbose=False)
acc_headers = {
"Authorization": auth_header,
}
verifier_params = {
"oauth_token": req_oauth_token,
"oauth_verifier": oauth_verifier,
}
response_acc = requests.post(access_endpoint_url, headers=acc_headers, json=verifier_params)
response_acc_text = response_acc.text
print(response_acc_text)
上から順に説明していきます。
リクエストトークンの取得
まず最初に行うのはリクエストトークンの取得です。
callback_url = "http://localhost:11230/"
request_endpoint_url = "https://api.twitter.com/oauth/request_token"
auth_header = create_oauth_header(
endpoint_url=request_endpoint_url,
oauth_consumer_key=oauth_consumer_key,
oauth_consumer_secret=oauth_consumer_secret,
oauth_token_secret="", # まだアクセストークンシークレット取得前なので空文字で OK
oauth_callback=callback_url, # ヘッダにも oauth_callback を含めないとデフォルトのコールバック先に飛ばされてしまう
verbose=False)
req_headers = {
"Authorization": auth_header,
}
request_token_params = {
"oauth_callback": callback_url,
}
response_req = requests.post(request_endpoint_url, headers=req_headers, json=request_token_params)
重要なポイントに絞って解説します。
リクエストトークンを取得する時点ではまだアクセストークンもアクセストークンシークレットも取得していません。そのため、oauth_token_secret
には空文字を指定します。また、コールバック先の指定として oauth_callback
を指定します。
そしてリクエストパラメータに oauth_callback
を指定してリクエストを送ります。このコールバック先の URL は、前述した通り、Twitter Developer Portal で設定しておいたものを指定する必要があります。もし登録していない URL を指定するとエラーになるので注意してください。
リクエストが成功すると以下のようなフォーマットでレスポンスが返ってきます。
oauth_token=<TOKEN>&oauth_token_secret=<SECRET>&oauth_callback_confirmed=true
ユーザに認可してもらう
ここから oauth_token
を取り出して次のステップに進みます。
# 返されたリクエストトークンを取り出す
oauth_token_kvstr = response_req_text.split("&")
token_dict = {x.split("=")[0]: x.split("=")[1] for x in oauth_token_kvstr}
req_oauth_token = token_dict["oauth_token"]
oauth_token
を取り出したら、それを利用してユーザに認可をしてもらうための URL を生成します。
今回は生成した URL に自分でアクセスし、そこで認可後に取得された verifier
を手入力する想定で実装しています。
authenticate_url = "https://api.twitter.com/oauth/authenticate"
# 自身で、生成された URL にアクセスし許可する
print("Please access the following URL and get the OAuth Verifier.")
print(f"{authenticate_url}?oauth_token={req_oauth_token}")
oauth_verifier = input("OAuth Verifierを入力してください> ")
verifier
を取得したらそれを入力して次に進める想定です。(今回はあくまで全体の流れを把握するためなので自身でそれぞれの処理を実行していることを想定しています)
手作業で入力した verifier
を用いてアクセストークンを取得するリクエストを送ります。
リクエスト処理は以下の通りです。
access_endpoint_url = "https://api.twitter.com/oauth/access_token"
auth_header = create_oauth_header(
endpoint_url=access_endpoint_url,
oauth_consumer_key=oauth_consumer_key,
oauth_consumer_secret=oauth_consumer_secret,
oauth_token_secret="", # まだアクセストークンシークレットは未取得なので空文字で OK
oauth_token=req_oauth_token, # リクエストトークン
oauth_verifier=oauth_verifier, # 手入力した verifier
verbose=False)
acc_headers = {
"Authorization": auth_header,
}
verifier_params = {
"oauth_token": req_oauth_token,
"oauth_verifier": oauth_verifier,
}
response_acc = requests.post(access_endpoint_url, headers=acc_headers, json=verifier_params)
ここもまた重要な点に絞って解説します。
今回のリクエストでは、前段で取得したトークンがあるため oauth_token
にそれを指定しています。また、oauth_verifier
には先ほど取得した verifier
を指定します。どちらもヘッダに含める必要がある点に注意してください。
そしてリクエストのパラメータとしても oauth_token
と oauth_verifier
を指定してリクエストを送ります。
無事にリクエストが成功すると以下のフォーマットでアクセストークンとアクセストークンシークレットが返却されます。
oauth_token=<TOKEN>&oauth_token_secret=<SECRET>&user_id=<USER_ID>&screen_name=<SCREEN_NAME>
これで無事、各 API リクエストに必要なアクセストークンとアクセストークンシークレットを取得することができました。
シグネチャ生成部分とアクセストークン取得部分が理解できれば、あとは各種 API リクエストの必要パラメータに沿って準備し、リクエストを投げることで投稿することができるようになります。
画像をアップロードする
ここからは実際に画像をアップロードしそれをツイートするまでを書いていきたいと思います。
しかしながら、シグネチャ生成、アクセストークン取得など一番大変なところは解説済みなので、以降はそこまで複雑なことはないと思います。
まず、画像アップロードができる API は現時点(2024.09)では v1.1 の API である https://upload.twitter.com/1.1/media/upload.json
です。
そしてこの API を利用するために OAuth 1.0a での認証が必要であることは冒頭で説明しました。ここではこの API に画像をアップロードする方法について見ていきます。
画像のアップロードになるのでリクエストにはバイナリデータを送ることができる multipart/form-data
形式を利用します。ということで、まずはリクエストしているコードを見てみます。
def post(files):
endpoint_url = "https://upload.twitter.com/1.1/media/upload.json"
oauth_consumer_key = os.environ.get("CONSUMER_KEY")
oauth_consumer_secret = os.environ.get("CONSUMER_SECRET")
oauth_token = os.environ.get("AUTH_TOKEN")
oauth_token_secret = os.environ.get("AUTH_TOKEN_SECRET")
auth_header = create_oauth_header(
endpoint_url=endpoint_url,
oauth_consumer_key=oauth_consumer_key,
oauth_consumer_secret=oauth_consumer_secret,
oauth_token=oauth_token, # リクエストトークンの交換で取得したアクセストークン
oauth_token_secret=oauth_token_secret, # とアクセストークンシークレット
verbose=False)
# リクエストヘッダーのセット
headers = {
"Authorization": auth_header,
}
print(f"Access the api [{endpoint_url}] ...")
response = requests.post(endpoint_url, headers=headers, files=files)
return response
ツイート投稿の API 呼び出しとほぼ同じ見た目になっているのが分かるかと思います。違いは files
をリクエストに含めている点です。
files
は dict
型になっており、パラメータ名は media
になっています。そしてその内容はファイルを読み込んだファイルオブジェクトになっています。
file = open(image_file_path, "rb")
files = {
"media": file,
}
上記理由により、実際にリクエストが送られる際のログを見てみると multipart/form-data
のフィールドとして media
が追加され、内容がファイルのバイナリとして送られているのが分かります。
send: b'--b37c71c90dca02b483a864b6a7b6e7b6\r\nContent-Disposition: form-data; name="media"; filename="logo.jpg"\r\n\r\n\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xe2\x02\xa0ICC_PROFILE\x00\xn...略'
画像のアップロードに成功するとアップロードした画像の media_ia
がレスポンスとして返されます。この media_id
を利用してツイート投稿をすることで画像付きポストができるようになります。
レスポンスは以下のような形で返ってきます。
{
'media_id': 1829075979844874240,
'media_id_string': '1829075979844874240',
'size': 41809,
'expires_after_secs': 86400,
'image': {
'image_type': 'image/jpeg',
'w': 400,
'h': 400
}
}
この media_id
を使って画像付きツイートを投稿していきます。
画像付きツイートを投稿する
いよいよ最後のパートです。画像アップロードまで完了しました。あとはアップロードした画像付きツイートを投稿することができれば当初の目的が達成されます。
ただ、ツイート投稿そのものはシグネチャ生成の時点で説明してあります。実は投稿内容に少し手を加えるだけで達成することができます。
具体的には以下のように、ツイート文に加えて media_id
を含めるだけです。言い換えると、 media_id
を含めれば画像付きツイートを、含めなければテキストツイートを投稿することができるということです。
media_id = response.json()["media_id_string"]
parameters = {
"text": tweet_text,
# Media ID を配列で指定する
"media": {
"media_ids": [media_id],
},
}
tweet_response = post_tweet_request(parameters)
post_tweet_request
はシグネチャ生成のときに解説したものと同様です。以下のようにユーティリティを読み込むときに rename しているだけです。
from post_tweet import request as post_tweet_request
これで画像付きツイートの投稿が完了です。
まとめ
画像のアップロードおよびツイート投稿そのものは単に API にリクエストを投げるだけなのでむずかしいことはありません。
むずかしさを増しているのは、OAuth 1.0a による認可フローとシグネチャ生成です。
冒頭でも書きましたが「画像付き投稿をプログラムから行う」という比較的需要がありそうなことが、一気通貫で分かりやすく書かれている記事がない印象です。
それはひとえに、認証フローの複雑さなどもあると思っています。
この記事が、画像付きツイートをしたい方の助けになれば幸いです。
告知
冒頭でも書きましたが、この機能は開発中の VR ゲームで画像付きツイートをさせたかったのがきっかけでした。Meta Quest 向けのゲームで、実際に剣を振ることで斬撃が出て食材を切る、タイミングゲーとなっています。
また、最近話題の生成 AI を利用し、カットした食材に応じて毎回様変わりする料理イラストを楽しむことができるゲームになっています。
4Gamers さんと Yahoo! ニュースにリリースが掲載されました!
Discussion