Hatena::ブログ(Diary)

nojimaの日記

2010-01-29

C#でOAuthでTwitter

| 04:17

OAuth認証でTwitterにアクセスするコードをC#で書いてみた。OAuthBase.csを参考にした。今のところ認証を通過することぐらいしかできないのでそのうち機能を追加していきたい。

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Configuration;
using System.Text;
using System.Web;
using System.Net;
using System.IO;

namespace Twitter
{
    class Auth {
        const string REQUEST_TOKEN_URL = "https://twitter.com/oauth/request_token";
        const string ACCESS_TOKEN_URL = "https://twitter.com/oauth/access_token";
        const string AUTHORIZE_URL = "https://twitter.com/oauth/authorize";

        private Random random = new Random();

        public string ConsumerKey { get; private set; }
        public string ConsumerSecret { get; private set; }
        public string RequestToken { get; private set; }
        public string RequestTokenSecret { get; private set; }
        public string AccessToken { get; private set; }
        public string AccessTokenSecret { get; private set; }
        public string UserId { get; private set; }
        public string ScreenName { get; private set; }

        public Auth(string consumerKey, string consumerSecret) {
            ServicePointManager.Expect100Continue = false;
            ConsumerKey = consumerKey;
            ConsumerSecret = consumerSecret;
        }

        public Auth(string consumerKey, string consumerSecret, string accessToken, string accessTokenSecret, string userId, string screenName) {
            ServicePointManager.Expect100Continue = false;
            ConsumerKey = consumerKey;
            ConsumerSecret = consumerSecret;
            AccessToken = accessToken;
            AccessTokenSecret = accessTokenSecret;
            UserId = userId;
            ScreenName = screenName;
        }

        public void GetRequestToken() {
            SortedDictionary<string, string> parameters = GenerateParameters("");
            string signature = GenerateSignature("", "GET", REQUEST_TOKEN_URL, parameters);
            parameters.Add("oauth_signature", UrlEncode(signature));
            string response = HttpGet(REQUEST_TOKEN_URL, parameters);
            Dictionary<string, string> dic = ParseResponse(response);
            RequestToken = dic["oauth_token"];
            RequestTokenSecret = dic["oauth_token_secret"];
        }

        public string GetAuthorizeUrl() {
            return AUTHORIZE_URL + "?oauth_token=" + RequestToken;
        }

        public void GetAccessToken(string pin) {
            SortedDictionary<string, string> parameters = GenerateParameters(RequestToken);
            parameters.Add("oauth_verifier", pin);
            string signature = GenerateSignature(RequestTokenSecret, "GET", ACCESS_TOKEN_URL, parameters);
            parameters.Add("oauth_signature", UrlEncode(signature));
            string response = HttpGet(ACCESS_TOKEN_URL, parameters);
            Dictionary<string, string> dic = ParseResponse(response);
            AccessToken = dic["oauth_token"];
            AccessTokenSecret = dic["oauth_token_secret"];
            UserId = dic["user_id"];
            ScreenName = dic["screen_name"];
        }

        public string Get(string url, IDictionary<string, string> parameters) {
            SortedDictionary<string, string> parameters2 = GenerateParameters(AccessToken);
            foreach (var p in parameters)
                parameters2.Add(p.Key, p.Value);
            string signature = GenerateSignature(AccessTokenSecret, "GET", url, parameters2);
            parameters2.Add("oauth_signature", UrlEncode(signature));
            return HttpGet(url, parameters2);
        }

        public string Post(string url, IDictionary<string, string> parameters) {
            SortedDictionary<string, string> parameters2 = GenerateParameters(AccessToken);
            foreach (var p in parameters)
                parameters2.Add(p.Key, p.Value);
            string signature = GenerateSignature(AccessTokenSecret, "POST", url, parameters2);
            parameters2.Add("oauth_signature", UrlEncode(signature));
            return HttpPost(url, parameters2);
        }

        private string HttpGet(string url, IDictionary<string, string> parameters) {
            WebRequest req = WebRequest.Create(url + '?' + JoinParameters(parameters));
            WebResponse res = req.GetResponse();
            Stream stream = res.GetResponseStream();
            StreamReader reader = new StreamReader(stream);
            string result = reader.ReadToEnd();
            reader.Close();
            stream.Close();
            return result;
        }

        string HttpPost(string url, IDictionary<string, string> parameters) {
            byte[] data = Encoding.ASCII.GetBytes(JoinParameters(parameters));
            WebRequest req = WebRequest.Create(url);
            req.Method = "POST";
            req.ContentType = "application/x-www-form-urlencoded";
            req.ContentLength = data.Length;
            Stream reqStream = req.GetRequestStream();
            reqStream.Write(data, 0, data.Length);
            reqStream.Close();
            WebResponse res = req.GetResponse();
            Stream resStream = res.GetResponseStream();
            StreamReader reader = new StreamReader(resStream, Encoding.UTF8);
            string result = reader.ReadToEnd();
            reader.Close();
            resStream.Close();
            return result;

        }

        private Dictionary<string, string> ParseResponse(string response) {
            Dictionary<string, string> result = new Dictionary<string, string>();
            foreach (string s in response.Split('&')) {
                int index = s.IndexOf('=');
                if (index == -1)
                    result.Add(s, "");
                else
                    result.Add(s.Substring(0, index), s.Substring(index + 1));
            }
            return result;
        }

        private string JoinParameters(IDictionary<string, string> parameters) {
            StringBuilder result = new StringBuilder();
            bool first = true;
            foreach (var parameter in parameters) {
                if (first)
                    first = false;
                else
                    result.Append('&');
                result.Append(parameter.Key);
                result.Append('=');
                result.Append(parameter.Value);
            }
            return result.ToString();
        }

        private string GenerateSignature(string tokenSecret, string httpMethod, string url, SortedDictionary<string, string> parameters) {
            string signatureBase = GenerateSignatureBase(httpMethod, url, parameters);
            HMACSHA1 hmacsha1 = new HMACSHA1();
            hmacsha1.Key = Encoding.ASCII.GetBytes(UrlEncode(ConsumerSecret) + '&' + UrlEncode(tokenSecret));
            byte[] data = System.Text.Encoding.ASCII.GetBytes(signatureBase);
            byte[] hash = hmacsha1.ComputeHash(data);
            return Convert.ToBase64String(hash);
        }

        private string GenerateSignatureBase(string httpMethod, string url, SortedDictionary<string, string> parameters) {
            StringBuilder result = new StringBuilder();
            result.Append(httpMethod);
            result.Append('&');
            result.Append(UrlEncode(url));
            result.Append('&');
            result.Append(UrlEncode(JoinParameters(parameters)));
            return result.ToString();
        }

        private SortedDictionary<string, string> GenerateParameters(string token) {
            SortedDictionary<string, string> result = new SortedDictionary<string, string>();
            result.Add("oauth_consumer_key", ConsumerKey);
            result.Add("oauth_signature_method", "HMAC-SHA1");
            result.Add("oauth_timestamp", GenerateTimestamp());
            result.Add("oauth_nonce", GenerateNonce());
            result.Add("oauth_version", "1.0");
            if (!string.IsNullOrEmpty(token))
                result.Add("oauth_token", token);
            return result;
        }

        public string UrlEncode(string value) {
            string unreserved = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
            StringBuilder result = new StringBuilder();
            byte[] data = Encoding.UTF8.GetBytes(value);
            foreach (byte b in data) {
                if (b < 0x80 && unreserved.IndexOf((char)b) != -1)
                    result.Append((char)b);
                else
                    result.Append('%' + String.Format("{0:X2}", (int)b));
            }
            return result.ToString();
        }

        private string GenerateNonce() {
            string letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
            StringBuilder result = new StringBuilder(8);
            for (int i = 0; i < 8; ++i)
                result.Append(letters[random.Next(letters.Length)]);
            return result.ToString();
        }

        private string GenerateTimestamp() {
            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalSeconds).ToString();
        }
    }

    class Program {
        const string CONSUMER_KEY = "hogehogehogehogehoge";
        const string CONSUMER_SECRET = "fugafugafugafugafugafugafugafuga";

        static void Main(string[] args)
        {
            Auth auth;
            var settings = Twitter.Properties.Settings.Default;

            if (string.IsNullOrEmpty((string)settings["AccessToken"])) {
                auth = new Auth(CONSUMER_KEY, CONSUMER_SECRET);

                // リクエストトークンを取得する
                auth.GetRequestToken();

                // ユーザーにRequestTokenを認証してもらう
                Console.WriteLine("次のURLにアクセスして暗証番号を取得してください:");
                Console.WriteLine(auth.GetAuthorizeUrl());
                Console.Write("暗証番号:");
                string pin = Console.ReadLine().Trim();

                // アクセストークンを取得する
                auth.GetAccessToken(pin);

                // 結果を表示する
                Console.WriteLine("AccessToken: " + auth.AccessToken);
                Console.WriteLine("AccessTokenSecret: " + auth.AccessTokenSecret);
                Console.WriteLine("UserId: " + auth.UserId);
                Console.WriteLine("ScreenName: " + auth.ScreenName);

                // アクセストークンを設定ファイルに保存する
                settings["AccessToken"] = auth.AccessToken;
                settings["AccessTokenSecret"] = auth.AccessTokenSecret;
                settings["UserId"] = auth.UserId;
                settings["ScreenName"] = auth.ScreenName;
                settings.Save();
            } else {
                // 設定ファイルから読み込む
                auth = new Auth(CONSUMER_KEY, CONSUMER_SECRET,
                                (string)settings["AccessToken"], (string)settings["AccessTokenSecret"],
                                (string)settings["UserId"], (string)settings["ScreenName"]);
            }

            // ↓ここらへんは後でちゃんとwrapしたい

            // タイムラインから3件取得してみる
            Dictionary<string, string> parameters = new Dictionary<string, string>();
            parameters.Add("count", "3");
            Console.WriteLine(auth.Get("http://twitter.com/statuses/home_timeline.xml", parameters));

            // ポストしてみる
            Console.WriteLine("いまどうしてる?");
            string status = Console.ReadLine();
            parameters.Clear();
            parameters.Add("status", auth.UrlEncode(status));
            Console.WriteLine(auth.Post("http://twitter.com/statuses/update.xml", parameters));
        }
    }
}

ServicePointManager.Expect100Continue = false; って書いてなくて2時間ぐらい悩んでた。

[追記]

上のコードは、パブリックドメインということにします。御自由にお使いください。

watanabewatanabe 2010/04/05 18:02 今、Twitterクライエントを作成しているのですが、
認証処理にこのコードを参考にさせてもらってもよろしいでしょうか?

nojima718nojima718 2010/04/05 21:29 大丈夫です。自由に使ってください。

MM 2010/05/15 01:05 非常に参考になります。貴重な技術情報に感謝!

ChishowChishow 2010/06/03 03:04 こちらのコード大変参考になりました。
Twitter関連のツールを作っているのですが、こちらのコードを流用させていただいてもよろしいでしょうか?

nojima718nojima718 2010/06/03 03:23 いいですよ

ChishowChishow 2010/06/03 08:14 ありがとうございますっ!

shingotadashingotada 2010/08/24 02:04 twitter の簡単アプリを作ろうと思って、検索してたらヒットさせていただきました。
記載していただいているコードを張り付けて動かしたら、以下のところでエラーが出ました。

var settings = Twitter.Properties.Settings.Default;

いろいろ調べてたら、アプリケーション設定の部分に記述が必要?そうな感じでした。
すみませんが、どのようにしたら使えるのか教えていただけると大変ありがたいです。

nojima718nojima718 2010/08/24 02:54 次のページに書いてある方法でString型のAccessToken, AccessTokenSecret, UserId, ScreenNameを登録すると動くと思います。
http://dobon.net/vb/dotnet/programing/mysettings.html

ちょっと今ソースコードが手元にないので記憶違いがあったらすみません。

shingotadashingotada 2010/08/24 10:33 ありがとうございます!
やってみます。

ちょこちょこ 2011/02/25 21:31 勉強&実装に使わせてもらいます。
ありがとうございます。

trktrk 2011/04/09 17:56 非常に参考になりました。
ありがとうございます!

spankyspanky 2011/06/23 11:55 大変参考になりました。ほんとにありがとうございます。

f6f6 2011/06/27 14:49 とても参考になりました。
ありがとうございます。

ゆきゆき 2011/06/29 20:11 Twitterの認証とタイムラインの取得に関してとても参考になりました。
ありがとうございます!

ああああ 2014/02/16 23:28 とりあえずコピペしてみたら
var settings = Twitter.Properties.Settings.Default;
この行で、「型または名前空間名 'Properties' は名前空間 'Twitter' に存在しません。アセンブリ参照が不足しています。」と出てしまいます・・・