はじめに
この記事は「Django Advent Calendar 2021」の16日目の記事です。
3年連続3回目の参加です。毎年ありがとうございます。
DRF + NuxtでWebサービスを作成しました。
2020年に作成したWebサービスを今年に入ってからDjango REST FrameworkとNuxt.jsで作り直しました。
前回作成した時は画像をツイートするために、一度作成した画像をローカルに保存してから自分で貼り付けてツイートしてもらう手順となっていましたが、今時そんな面倒なサービス使われないと思い、今回はTwitter APIを利用してボタン一発で画像付きツイートできるように改修しました。
その際に色々調べましたが、なかなかDjangoで認証画面を経由して画像付きツイートする一気通貫な記事がなかったので備忘録として記事を書こうと思い立ちました。
そもそもTwitter API自体がよくわからん・・・難しくない?
Twitter APIでユーザー認証してからツイートする流れ
まずはどういう流れでDjangoとTwitter APIでユーザー認証して、ツイートするか確認します。
必要なもの
- API KEY
- API SECRET KEY
以上は、Twitterデベロッパーサイトでアプリを申請した時に取得します。
Twitter APIを利用する場合はデベロッパーサイトで申請する必要がありますが、解説はここだけで長くなってしまうので割愛します。ググってオナシャス。
認証手順
- コンシューマーアプリケーション(今回はDjango)がリクエストトークンを取得するためのリクエストを作成する
- ユーザーに認証してもらい、コンシューマーアプリケーションにリクエストトークンを送信する
- リクエストトークンからアクセストークンを取得する
- 3で受け取ったアクセストークンとシークレットトークンを使ってツイートする
Djangoでの処理
手順1にて、API KEYとAPI SECRET KEYをhttps://api.twitter.com/oauth/request_token
にPOSTで送ると、認証用URLが返ってきますのでリダイレクトさせます。
リダイレクト先はよくみるあの画面です。
この画面で「連携アプリを認証」をクリックすると、デベロッパーサイトで指定したcallback用URLにGETパラメータ付きで返ってきますので、Django側でcallbackを受け取ってあげる必要があります。
このパラメータにoauth_token
とoauth_verifier
がついてくるので、oauth_verifier
を使ってユーザーのACCESS_TOKEN_SECRET
を取得します。
最終的には以下のパラメータを使ってツイートします。
- API KEY(Twitterデベロッパーサイトで取得)
- API SECRET KRY(Twitterデベロッパーサイトで取得)
- oauth_verifier(ユーザー認証後のリダイレクト時に取得、ACCESS_TOKEN_SECRETを取得するために必要)
- ACCESS_TOKEN(oauth_tokenとしてユーザー認証後のリダイレクト時に取得)
- ACCESS_TOKEN_SECRET(oauth_verifierを利用して取得)
ユーザーとして使う時は認証画面うっとーしーなーと思っていましたが、実装するとなると結構面倒です。
これからは感謝しながら使います(テノヒラクルー
tweepy
PythonでTwitter APIを利用する際によく利用されるライブラリです。
今回はこれを使ったり使わなかったりしながら解説していきます。
実際のコード
サンプルプログラムの全容は以下からご確認ください。
お試し方法もREADMEに記載してあります。
まずは自分のアカウントでツイートする
Twitterデベロッパーサイトで申請した時のアカウントでツイートする場合は比較的簡単です。
from django.shortcuts import redirect
import tweepy
def tweet_myaccount(self):
auth = tweepy.OAuthHandler('API_KEY', 'API_SECRET_KEY')
auth.set_access_token('ACCESS_TOKEN', 'ACCESS_TOKEN_SECRET')
api = tweepy.API(auth)
api.update_status("これはテスト投稿です。tweepyを利用して投稿しています。")
# Twitterにリダイレクトする
return redirect('https://twitter.com/home')
'API_KEY'などの部分は実際のキーを入れてください。
ただし実際のプロジェクトでは環境変数に入れたりするなど、直接書き込まないでください。
成功するとこんな感じでツイートできます。
ね?簡単でしょ?
ユーザー認証をしてツイートする
続いてはこういうWebサービスでよくある、認証後にその人としてツイートするための処理です。
from django.shortcuts import redirect
import tweepy
def tweet(self):
# 認証準備
auth = tweepy.OAuthHandler('API_KEY', 'API_SECRET_KEY')
# Twitter認証画面URLを取得する
try:
redirect_url = auth.get_authorization_url()
except tweepy.TweepyException:
print("Error! Failed to get request token.")
# ここで認証ページに遷移する
return redirect(redirect_url)
def callback(request):
# 認証画面でキャンセルした時の戻り先
if 'denied' in request.GET.dict():
return redirect('http://127.0.0.1:8000/')
# 認証した場合の処理
# ツイートするユーザーのトークンを取得する準備
auth = tweepy.OAuthHandler('API_KEY', 'API_SECRET_KEY')
auth.request_token['oauth_token'] = request.GET['oauth_token']
auth.request_token['oauth_token_secret'] = oauth_verifier = request.GET['oauth_verifier']
# ツイートするユーザーのシークレットトークンを取得する
try:
auth.get_access_token(oauth_verifier)
except tweepy.TweepyException:
print("Error! Failed to get request token.")
# ツイートする
auth.set_access_token(auth.access_token, auth.access_token_secret)
api = tweepy.API(auth)
api.update_status("認証画面を経由して投稿しています。これはテスト投稿です。")
# Twitterにリダイレクトする
return redirect('https://twitter.com/home')
まずはtweet()
にて認証用URLを取得し、リダイレクトさせています。
「連携アプリを認証」をクリックすると、GETパラメータにoauth_token
と oauth_verifier
をつけてTwitterデベロッパーサイトのCALLBACK URLSに指定したURLにアクセスします。(Djangoのログや、POSTMANを使うと/callback?oauth_token=xxxx&oauth_verifier=xxxx
の形でアクセスしてくるのが分かります)
Django側のcallback(request)
で受けて、GETパラメータを取得します。
取得したoauth_token
と oauth_verifier
を利用してACCESS_TOKEN_SECRET
を取得しています。
長い道のりでしたがこれで必要なパラメータが揃ったのでツイートします。
※私の猫アカウントです
たぶん動くと思いますが動かなかったらご連絡ください。
サンプルで何回も試そうとすると、同じ文言はツイートできないのでエラーになる場合があります。
その場合は文言を変更するとツイートできるはずです。
任意の文言と画像でツイートする(ぼく将オーダーの場合)
フロントエンド側(Nuxt)で作った画像と入力した文言でツイートしたかったのですが、POSTでバックエンド側(DRF)に送っても認証のためリダイレクトしちゃうので、どこかに保存しておかないとせっかく作った画像も文言も消えてしまうんですね。
ちなみに画像はHTML2canvasで作っていますので、基本的にはbase64です。
本来ならCookieとかローカルストレージとかセッションに保存しておけばいいのでしょうが、試してみたところできなかった(自分の理解不足の)ため、データベースに保存しています。画像とはいえbase64形式であれば文字列として処理できますからね。
callback後の処理でデータベースから画像(base64)と文言を取り出していますが、tweepyではbase64そのままではアップロードできなかったので、生のTwitter APIを利用して画像アップロード→ツイートという方法を取っています。
以下のコードはDRFで作成しています。
実際にぼく将オーダーで使用しているコードのため、サンプルに含まれません。
class ImageTweetAPIView(views.APIView):
"""作成した画像をツイートするAPIクラス
Args:
img_base64 (String): imageのbase64文字列
tweet_text (String): ツイートする文字列
"""
def post(self, request):
# img_base64とtweet_textをDBに保存する
data = request.data
# DBにデータを保存
img_base64 = data['img_base64']
tweet_text = data['tweet_text']
# Twitter認証画面URLを取得する
auth = tweepy.OAuthHandler(getenv('CONSUMER_KEY'), getenv('CONSUMER_SECRET'))
try:
redirect_url = auth.get_authorization_url()
except tweepy.TweepyException:
print("Error! Failed to get request token.")
oauth_token = parse.parse_qs(parse.urlparse(redirect_url).query)['oauth_token'][0]
# Tweetテーブルに保存する
t = Tweet(oauth_token=oauth_token, img_base64=img_base64, tweet_text=tweet_text)
t.save()
# ここで認証ページに遷移する
return redirect(redirect_url)
class TwitterCallbackAPIView(views.APIView):
"""Twitter認証のコールバック
Args:
oauth_token(String): ユーザートークン
oauth_verifier(String): ユーザー認証コード
"""
def get(self, request):
# 認証画面でキャンセルした時の戻り先
if 'denied' in request.query_params.dict():
# denied=oauth_tokenをキーにTweetからデータを削除する
t = Tweet.objects.get(oauth_token=request.query_params['denied'])
t.delete()
return redirect(getenv('BACK_URL'))
# 認証した場合の処理
# ツイートするユーザーのトークンを取得する準備
auth = tweepy.OAuthHandler(getenv('CONSUMER_KEY'), getenv('CONSUMER_SECRET'))
auth.request_token['oauth_token'] = oauth_token = request.query_params['oauth_token']
auth.request_token['oauth_token_secret'] = oauth_verifier = request.query_params['oauth_verifier']
# ツイートするユーザーのシークレットトークンを取得する
try:
auth.get_access_token(oauth_verifier)
except tweepy.TweepyException:
print("Error! Failed to get request token.")
# oauth_tokenをキーにしてTweetからデータを取得する
t = Tweet.objects.get(oauth_token=oauth_token)
img_base64 = t.img_base64
# 画像をUploadする
oauth = OAuth1(getenv('CONSUMER_KEY'), getenv('CONSUMER_SECRET'), auth.access_token, auth.access_token_secret)
headers = {'Content-Transfer-Encoding': 'base64'}
upload_payload = {"media_data": img_base64}
upload_response = requests.post(getenv('UPLOAD_API'), auth=oauth, headers=headers, data=upload_payload)
upload_response_dict = json.loads(upload_response.text)
media_id = upload_response_dict['media_id']
# ツイートする
tweet_text = t.tweet_text
update_payload = {"status": tweet_text, "media_ids": media_id}
update_response = requests.post(getenv('TWEET_API'), auth=oauth, headers=headers, data=update_payload)
# Tweetからデータを削除する
t.delete()
# Twitterにリダイレクトする
return redirect('https://twitter.com/home')
base64をデコードしてどこかに保存してからであればtweepyでも画像付きでツイートできますが、そのためにS3使ったりするのも面倒だったのでこうなりました。
もっといいやり方ないですかね・・・?
おわりに
もしツイートできないなどバグがありましたらここのコメントかTwitterにてフィードバックいただけると幸いです。
サンプルのバグもありましたら同様にここのコメントかGitHubのissueでもいいですし、プルリクいただければ対応します。
参考
Twitterデベロッパーサイト
メディアのアップロード
Tweepy Documentation
【Python3】tweepyで認証情報(AccessToken)の取得方法
Python / TweepyでOAuth認証してツイートする
ユーザー認証を行ってAccess Token, Access Token Secretを取得する
[Python]Tweepyでツイートを画像付きで投稿する
TweepyでTwitterに投稿する
Tweepy で Twitter に画像付きのツイートをする
Pythonで『tweepy』を使いこなす方法【テキスト・改行・複数画像添付】
Tweepyを使って、PythonでTwitterのAPIを超簡単に操作する
.env ファイルで環境変数を設定する (python-dotenv)
Django URLパラメータをセッションに保存してテンプレートで利用する
[Python]Base64でエンコードされた画像データをデコードする