【TwitterAPI】POST statuses/update APIの使い方( Java で ツイートbot)

アクセストークンの取得

ツイートの検索(SearchAPI)や、ツイートの埋め込み(oEmbedAPI)を行なう場合は、ベアラートークン(bearer token)を使って認証できましたが、ツイートを発行する場合には、アカウントと紐づいている必要があるので、この方法は使えません。自分のアカウントからAPIへアクセスしたい場合には、dev.twitter.comから取得するトークンを使って認証を行います。
下記の4つの項目をメモしておいてください。

スポンサーリンク

OAuth 1.0a HMAC-SHA1 署名の生成

パラメータの作成

キー
status ツイートに含める文字列
oauth_consumer_key 上記で取得した CONSUMER_KEY
oauth_nonce リクエストごとに生成する重複のない文字列
oauth_signature_method HMAC-SHA1
oauth_timestamp リクエストが生成されたタイミングのUnixエポック秒
oauth_token 上記で取得した ACCESS_TOKEN
oauth_version 1.0
  1. 上記のキーと値をそれぞれURLエンコードする。
  2. キーと値を "=" で連結する。
  3. すべてのパラメータを "&" で連結する。(キーのアルファベット順で連結する必要があります。)

そうすると、下記のような文字列が出来上がります。

oauth_consumer_key=xvz1evFS4wEEPTGEFPHBog&oauth_nonce=kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1318622958&oauth_token=370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb&oauth_version=1.0&status=Hello%20Ladies%20%2B%20Gentlemen%2C%20a%20signed%20OAuth%20request%21

署名キーの生成

上記で取得した CONSUMER_SECRET と ACCESS_TOKEN_SECRET をURLエンコードして、"&" で連結したものです。
そうすると、下記のような文字列が出来上がります。

kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw&LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE

署名データの生成

下記の順で文字列を組み立てます。

  1. "POST" (HTTPメソッドを大文字に変換したもの)
  2. "&"
  3. "https://api.twitter.com/1.1/statuses/update.json" をURLエンコードしたもの
  4. "&"
  5. 作成したパラメータ文字列

そうすると下記のような文字列が出来上がります。

POST&https%3A%2F%2Fapi.twitter.com%2F1.1%2Fstatuses%2Fupdate.json&include_entities%3Dtrue%26oauth_consumer_key%3Dxvz1evFS4wEEPTGEFPHBog%26oauth_nonce%3DkYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318622958%26oauth_token%3D370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb%26oauth_version%3D1.0%26status%3DHello%2520Ladies%2520%252B%2520Gentlemen%252C%2520a%2520signed%2520OAuth%2520request%2521

署名の作成

java で HMAC-SHA1 形式の署名を作成する場合は下記のようにします。
引数に署名のキーと署名のデータを渡すと、署名文字列を返します。

import java.util.Base64;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;


public String getSignature(String key, String data) {
	SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), "HmacSHA1");
	Mac mac = Mac.getInstance(signingKey.getAlgorithm());
	mac.init(signingKey);
	byte[] rawHmac = mac.doFinal(data.getBytes());
	String signature = Base64.getEncoder().encodeToString(rawHmac);
	return signature;
}

Authorizationヘッダの作成

キー
oauth_consumer_key 先に取得しておいた CONSUMER_KEY
oauth_nonce リクエストごとに生成する重複のない文字列
oauth_signature 上記で作成した署名文字列
oauth_signature_method HMAC-SHA1
oauth_timestamp リクエストが生成されたタイミングのUnixエポック秒
oauth_token 先に取得しておいた ACCESS_TOKEN
oauth_version 1.0
  1. 上記のキーと値をそれぞれURLエンコードする。
  2. 値を "(ダブルクォーテーション)で囲む。
  3. 1. と 2. を "=" で連結する。
  4. すべてのパラメータを ", " で連結する。(カンマと半角スペース)
  5. 先頭に "OAuth " を付ける。(半角スペースを忘れずに)

そうすると下記のような文字列が出来上がります。これをリクエストのAuthorizationヘッダに設定します。

OAuth oauth_consumer_key="xvz1evFS4wEEPTGEFPHBog", oauth_nonce="kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg", oauth_signature="tnnArxj06cWHq44gCs1OSKk%2FjLY%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1318622958", oauth_token="370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb", oauth_version="1.0"

実装例

Java

下記のライブラリを使用しています。

  • OkHttp3

下記のハイライト部分はプロパティファイル等に定義するのが良いと思います。

  • CONSUMER_KEY
  • CONSUMER_SECRET
  • OAUTH_TOKEN
  • OAUTH_TOKEN_SECRET
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.Base64;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class TwitterBotService {
	
	public static final String CONSUMER_KEY       = "your consumer key here.";
	public static final String CONSUMER_SECRET    = "your consumer secret here.";
	public static final String OAUTH_TOKEN        = "your oauth token here.";
	public static final String OAUTH_TOKEN_SECRET = "your oauth token secret here.";
	
	
	public static void main(String[] args) throws Exception {
		TwitterBotService app = new TwitterBotService();
		app.tweet("ツイートサンプル #test");
	}
	
	
	/**
	 * OAuth1.0用パラメータの作成(oauth_signatureは除く)
	 * @return OAuth1.0用パラメータ
	 */
	private SortedMap<String, String> createOauthParamsExceptSignature() {
		// キーのアルファベット順である必要があるので、SortedMapを使う
		SortedMap<String, String> params = new TreeMap<>();
		params.put("oauth_consumer_key", CONSUMER_KEY);
		params.put("oauth_signature_method", "HMAC-SHA1");
		params.put("oauth_timestamp", String.valueOf(Instant.now().getEpochSecond()));
		params.put("oauth_nonce", String.valueOf(Math.random()));
		params.put("oauth_version", "1.0");
		params.put("oauth_token", OAUTH_TOKEN);
		return params;
	}
	
	
	/**
	 * パラメータ文字列の作成
	 * @param oauthParams OAuth1.0用パラメータ
	 * @param status ツイート文言
	 * @return パラメータ文字列
	 */
	private String createParamsString(SortedMap<String, String> oauthParams, String status) {
		// パラメータをコピー
		SortedMap<String, String> params = new TreeMap<>(oauthParams);
		
		// status を追加
		params.put("status", urlEncode(status));
		
		Iterator<Entry<String, String>> ite = params.entrySet().iterator();
		
		StringBuilder sb = new StringBuilder();
		while (ite.hasNext()) {
			Entry<String, String> entry = ite.next();
			sb.append("&");
			sb.append(entry.getKey());
			sb.append("=");
			sb.append(entry.getValue());
		}
		
		return sb.substring(1);
	}
	
	/**
	 * 署名キーの作成
	 * @return 署名キー
	 */
	private String createSignatureKey() {
		return urlEncode(CONSUMER_SECRET) + "&" + urlEncode(OAUTH_TOKEN_SECRET);
	}
	
	/**
	 * 署名データの作成
	 * @param method HTTPメソッド(大文字)
	 * @param urlStr URL
	 * @param paramStr パラメータ文字列
	 * @return 署名データ
	 */
	private String createSignatureData(String method, String urlStr, String paramStr) {
		return method + "&" + urlEncode(urlStr) + "&" + urlEncode(paramStr);
	}
	
	/**
	 * 署名の算出
	 * @param key 署名キー
	 * @param data 署名データ
	 * @return 署名文字列
	 */
	private String calcSignature(String key, String data) {
		try {
			SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), "HmacSHA1");
			Mac mac = Mac.getInstance(signingKey.getAlgorithm());
			mac.init(signingKey);
			byte[] rawHmac = mac.doFinal(data.getBytes());
			System.out.println(rawHmac);
			return Base64.getEncoder().encodeToString(rawHmac);
		} catch (NoSuchAlgorithmException | InvalidKeyException e) {
			throw new RuntimeException(e);
		}
	}
	
	
	
	/**
	 * Authorizationヘッダ文字列の作成
	 * @param oauthParams OAuth1.0用パラメータ
	 * @return Authorizationヘッダ文字列
	 */
	private String createAuthorizationHeader(SortedMap<String, String> oauthParams) {
		
		Iterator<Entry<String, String>> ite = oauthParams.entrySet().iterator();
		
		StringBuilder sb = new StringBuilder();
		while (ite.hasNext()) {
			Entry<String, String> entry = ite.next();
			sb.append(", ");
			sb.append(entry.getKey());
			sb.append("=\"").append(urlEncode(entry.getValue())).append("\"");
		}
		
		return "OAuth " + sb.substring(2);
	}
	
	/**
	 * URLエンコード
	 * @param str 対象文字列
	 * @return URLエンコードした文字列
	 */
	private static String urlEncode(String str) {
		try {
			return URLEncoder.encode(str, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * ツイート発行
	 * @param status ツイート文言
	 * @throws Exception リクエスト失敗
	 */
	public void tweet(String status) throws Exception {
		String method = "POST";
		String urlStr = "https://api.twitter.com/1.1/statuses/update.json";

		// 署名(oauth_signature)以外の oauth_* パラメータを作成
		SortedMap<String, String> oauthParams = createOauthParamsExceptSignature();
		
		// 署名を算出
		String key = createSignatureKey();
		String data = createSignatureData(method, urlStr, createParamsString(oauthParams, status));
		String signature = calcSignature(key, data);
		
		// 署名を oauth_* パラメータに追加
		oauthParams.put("oauth_signature", signature);

		// Authorizationヘッダを作成
		String authorizationHeader = createAuthorizationHeader(oauthParams);

		
		// HTTPリクエストbody に status (ツイート文言) を設定
		RequestBody body = RequestBody.create(
				MediaType.parse("application/x-www-form-urlencoded;charset=UTF-8"),
				"status=" + urlEncode(status)); // ツイート文言
		
		// リクエスト発行
		Request request = new Request.Builder()
		        .url(urlStr)
		        .addHeader("Authorization", authorizationHeader)
		        .post(body)
		        .build();
		OkHttpClient client = new OkHttpClient();
		Response response = client.newCall(request).execute();
		String responseStr = response.body().string();
		
		System.out.println(responseStr);
	}
}

エラーコードが返ってきた場合

同じ文言を短時間のうちに2度ツイートすると、下記のようなエラーでブロックされる仕様です。文言を変えてうまくいくかどうかを試しましょう。

{"errors":[{"code":32,"message":"Could not authenticate you."}]}

参考

  • https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update.html
  • https://developer.twitter.com/en/docs/basics/authentication/guides/creating-a-signature.html
  • https://developer.twitter.com/en/docs/basics/authentication/guides/authorizing-a-request.html
  • https://developer.twitter.com/en/docs/basics/response-codes.html
スポンサーリンク
関連キーワード
Java, TwitterAPIの関連記事
  • 【TwitterAPI】POST statuses/update APIの使い方( Java で ツイートbot)
  • 【TwitterAPI】Search API の使い方(Java編)
  • 【Java】Jacksonを使ったJSONの解析(エンコード、文字列化)
  • Jacksonで文字列をオブジェクトへ変換
    【Java】Jacksonを使ったJSONの解析(導入とデコード)
  • JavaでHTTPクライアント
    【Java】OkHttp で HTTP クライアントを作成する方法
おすすめの記事