2025年3月8日土曜日

Twitter(現X)にAPI経由でつぶやく2025年版

Twitter(現X)のAPIが大きく制限されてから久しいですが、久しぶりに最新のAPIを触りましたので記録しておきます。

まず、Twitter APIの認証にはOAuth 1.0aとOAuth 2.0の2種類がサポートされていますが、ユースケースによってそれらを使い分ける必要があります。2024年10月頃まではOAuth 2.0経由のツイートの投稿ができていたようですが、その後使えなくなったようです。

2025年現在の対応表は以下のようになっています。

今回はツイートを投稿しますので、OAuth 1.0a認証を行います。

APIキーの取得

何はともあれAPIキーを取得しなければ始まりません。Twitter Developer Accountにアクセスして、アプリを登録し、キーを確認します。

ここにある「API Key and Secret」と「Access Token and Secret」の計4種類を控えておきます。 API Key and Secretはアプリ特有のもの、Access Token and Secretは投稿するアカウント特有のものですが、そもそも無料アプリでは1アプリ&1アカウントまでしか使用できないので、これをそのまま使えば良いでしょう。

コード

投稿は以下のコードにて行います。

public class TwitterClient(HttpClient httpClient, string consumerKey, string consumerSecret, string accessToken, string tokenSecret)
{
    public async Task<HttpResponseMessage> PostTweetAsync(string text)
    {
        var timstamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
        var nonce = CreateNonce();
        var body = JsonSerializer.Serialize(new Dictionary<string, string>() { { "text", text } });
        var uri = new Uri("https://api.twitter.com/2/tweets");

        var request = new HttpRequestMessage {
            RequestUri = uri,
            Method = HttpMethod.Post,
            Content = new StringContent(body, Encoding.ASCII, "application/json")
        };

        var signatureBase64 = CreateSignature(uri.ToString(), "POST", nonce, timstamp);

        request.Headers.Authorization =
            new System.Net.Http.Headers.AuthenticationHeaderValue("OAuth",
                $@"oauth_consumer_key=""{Uri.EscapeDataString(consumerKey)}""," +
                $@"oauth_token=""{Uri.EscapeDataString(accessToken)}""," +
                $@"oauth_signature_method=""HMAC-SHA1""," +
                $@"oauth_timestamp=""{Uri.EscapeDataString(timstamp)}""," +
                $@"oauth_nonce=""{Uri.EscapeDataString(nonce)}""," +
                $@"oauth_version=""1.0""," +
                $@"oauth_signature=""{Uri.EscapeDataString(signatureBase64)}""");

        return await httpClient.SendAsync(request);
    }

    private string CreateSignature(string url, string method, string nonce, string timestamp)
    {
        var query = HttpUtility.ParseQueryString("");
        query.Add("oauth_consumer_key", consumerKey);
        query.Add("oauth_nonce", nonce);
        query.Add("oauth_signature_method", "HMAC-SHA1");
        query.Add("oauth_timestamp", timestamp);
        query.Add("oauth_token", accessToken);
        query.Add("oauth_version", "1.0");

        var signatureBaseString = $"{method}&{Uri.EscapeDataString(url)}&{Uri.EscapeDataString(query.ToString() ?? "")}";
        var compositeKey = $"{Uri.EscapeDataString(consumerSecret)}&{Uri.EscapeDataString(tokenSecret)}";

        using var hasher = new HMACSHA1(Encoding.ASCII.GetBytes(compositeKey));
        return Convert.ToBase64String(hasher.ComputeHash(Encoding.ASCII.GetBytes(signatureBaseString)));
    }

    static readonly Random random = new Random();

    private static string CreateNonce()
    {
        var nonce = new StringBuilder(32);

        for(int i = 0; i < 32; i++) {
            var rnd = random.Next(62);
            if(rnd < 26)
                nonce.Append((char)('A' + rnd));
            else if(rnd < 52)
                nonce.Append((char)('a' + rnd - 26));
            else
                nonce.Append((char)('0' + rnd - 52));
        }

        return nonce.ToString();
    }    
}

コンストラクタに引き渡すconsumerKey, consumerSecret, accessToken, tokenSecretは先ほど控えた4つのキーです。そのうえでPostTweetAsyncメソッドに投稿内容を投げてあげればつぶやけます。意外と簡単ですね。Twitter Developer Accountのサイトからキーを全て入手できるので、認証の面倒な手続きを実装する必要もありません。

ちなみに、 無料アカウントでは24時間あたり17件までしかツイートできません。実装したアプリをいろいろ試しているうちにあっという間に返ってくるHTTPステータスコードが429 Too Many Requestsになってしまいます。そうなったら諦めて24時間待ちましょう。HTTP Responseの詳細の中に"x-user-limit-24hour-reset"というものがあり、これをUnix timeとして読むと制限が解除される日時がわかります。

まあこんなんじゃ無料でAPIをいじってみようかとか、ライブラリ作って公開しようかとかいう人が全然現れなくなるのもやむを得ないですよね。まあ、APIを叩いてほしくないからこういうふうにしているのでしょうが。