自動でTweetするBotを作ろうと思って、TwitterのREST APIを使ってみたのだけど、アルファベット以外の文字をTweetするとOAuthに失敗してハマったのでメモしておきます。
TwitterのBotを作る記事はたくさんネット上に転がってるので、ここでは特に説明しないですが、僕が参考にしたのはこの記事。
https://blogs.msdn.microsoft.com/henrikn/2012/02/16/extending-httpclient-with-oauth-to-access-twitter/
で、この中で使っているOAuthBase.csはリンク切れだったのでGitHubにあったのを使ってます。
https://gist.github.com/tsupo/112124
ただ、このOAuthBase.csだとAuthenticationHeaderを作ってくれないのでそこは自分で作成。
1 2 3 4 5 6 7 8 |
string authHeader = "oauth_consumer_key=\"" + _consumerKey + "\"," + "oauth_nonce=\"" + nonce + "\"," + "oauth_signature=\"" + System.Web.HttpUtility.UrlEncode(signature) + "\"," + "oauth_signature_method=\"HMAC-SHA1\"," + "oauth_timestamp=\"" + timestamp + "\"," + "oauth_token=\"" + _token + "\"," + "oauth_version=\"1.0\""; request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("OAuth", authHeader); |
TweetするコードはHttpClientを使ってこんな感じ。
1 2 3 4 5 |
string message = "Test"; // Tweetするメッセージ. HttpClient httpClient = new HttpClient(new OAuthMessageHandler(new HttpClientHandler())); Uri uri = new Uri("https://api.twitter.com/1.1/statuses/update.json?status=" + message); Task<HttpResponseMessage> tweetTask = httpClient.PostAsync(uri, null); tweetTask.Wait(); |
これで「Test」みたいなアルファベットだけのメッセージならTweetできたのだけど「a=b」みたいなのをTweetするとOAuthに失敗する(401が返る)。UriのコンストラクタはmessageをUrlEncodeしてくれるみたいで、こんな風に明示的にUrlEncodeする必要はないし、このように変えてもやはりOAuthに失敗する。
1 |
Uri uri = new Uri("https://api.twitter.com/1.1/statuses/update.json?status=" + System.Web.HttpUtility.UrlEncode(message), true); |
原因は、.NET FrameworkのUrlEncodeがOAuthが要求しているRFC 3986に従っていないため。何がいけないかというと、例えば「=」をRFC 3986に従ってUrl Encodeすると「%3D」とならなければいけないのだけど、.NETのは「%3d」と小文字の「d」を使ってしまうのであった。当然これでシグネチャを作ると違う結果になってOAuthに失敗するというわけだ。
というわけで自分で作ったUrlEncodeがこちら。OAuthBase.csにもUrlEncodeがあるんだけど、UTF-8に変換してないからこっちの方がよいと思う。(OAuthBase.csでは文字列は一度UrlEncodeされているという前提だから問題ない)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
static string UrlEncode(string src) { byte[] data = System.Text.Encoding.UTF8.GetBytes(src); System.Text.StringBuilder dst = new System.Text.StringBuilder(); foreach (byte c in data) { if ((0x30 <= c && c <= 0x39) // 0 - 9 || (0x41 <= c && c <= 0x5A) // A - Z || (0x61 <= c && c <= 0x7A) // a - z || c == 0x2D || c == 0x2E // -, . || c == 0x5F || c == 0x7E) // _, ~ { dst.Append((char)c); } else { dst.Append('%'); dst.Append(String.Format("{0:X2}", (int)c)); } } return dst.ToString(); } |
同じようにハマった人の助けになれば幸いです。