Using OAuth to Send Updates to Twitter Using C#

12/9/2009

Well I'm not sure if it's better. To be honest it's a bit more difficult to set up initially but it's a bit more secure than the way I showed before. Anyway, this year (in fact early this year), Twitter started a transition to using OAuth. Since I'm new to messing with the service, I had no idea. But then again I'm pretty oblivious to most things, so I might have missed that information even if I had been an early adopter. That being said, I've talked about OAuth before and I'll assume that you've read the post here. If not take a look before looking at the code below. Anyway, enough stalling, let's jump into some code:

   1: /*
   2: Copyright (c) 2010 <a href="http://www.gutgames.com">James Craig</a>
   3: 
   4: Permission is hereby granted, free of charge, to any person obtaining a copy
   5: of this software and associated documentation files (the "Software"), to deal
   6: in the Software without restriction, including without limitation the rights
   7: to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   8: copies of the Software, and to permit persons to whom the Software is
   9: furnished to do so, subject to the following conditions:
  10: 
  11: The above copyright notice and this permission notice shall be included in
  12: all copies or substantial portions of the Software.
  13: 
  14: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16: FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17: AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18: LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20: THE SOFTWARE.*/
  21:  
  22: #region Usings
  23: using System;
  24: using System.Text.RegularExpressions;
  25: using Utilities.Web.OAuth;
  26:  
  27: #endregion
  28:  
  29: namespace Utilities.Web.Twitter
  30: {
  31:     /// <summary>
  32:     /// Helper class to deal with Twitter
  33:     /// </summary>
  34:     public class Twitter : OAuth.OAuth
  35:     {
  36:         #region Constructor
  37:  
  38:         /// <summary>
  39:         /// Constructor
  40:         /// </summary>
  41:         public Twitter()
  42:             : base()
  43:         {
  44:             this.SignatureType = Signature.HMACSHA1;
  45:         }
  46:  
  47:         #endregion
  48:  
  49:         #region Public Functions
  50:  
  51:         /// <summary>
  52:         /// Gets a request token/token secret
  53:         /// </summary>
  54:         /// <param name="Token">The request token</param>
  55:         /// <param name="TokenSecret">The request token secret</param>
  56:         public void GetRequestToken(out string Token, out string TokenSecret)
  57:         {
  58:             this.Token = "";
  59:             this.TokenSecret = "";
  60:             this.Method = HTTPMethod.GET;
  61:             this.Url = new Uri("http://twitter.com/oauth/request_token");
  62:             REST.REST RestHelper = new Utilities.Web.REST.REST();
  63:             RestHelper.Url = new Uri(GenerateRequest());
  64:             string Value = RestHelper.GET();
  65:             Regex TokenRegex = new Regex("oauth_token=(?<Value>[^&]*)");
  66:             Match TempToken = TokenRegex.Match(Value);
  67:             Token = TempToken.Groups["Value"].Value;
  68:             Regex TokenSecretRegex = new Regex("oauth_token_secret=(?<Value>[^&]*)");
  69:             Match TempTokenSecret = TokenSecretRegex.Match(Value);
  70:             TokenSecret = TempTokenSecret.Groups["Value"].Value;
  71:         }
  72:  
  73:         /// <summary>
  74:         /// Gets the location of the authorization site (requires
  75:         /// request token/token secret)
  76:         /// </summary>
  77:         /// <returns>The location that the user must go in order to
  78:         /// authorize the app and get the authorization PIN</returns>
  79:         public string GetAuthorizationSite()
  80:         {
  81:             this.Method = HTTPMethod.GET;
  82:             this.Url = new Uri("https://twitter.com/oauth/authorize");
  83:             this.AddParameter("oauth_callback", "oob");
  84:             return new Uri(GenerateRequest()).ToString();
  85:         }
  86:  
  87:         /// <summary>
  88:         /// Gets the access token/token secret which are used in actual calls
  89:         /// (requires the PIN from the authorization site and the request token
  90:         /// and request token secret)
  91:         /// </summary>
  92:         /// <param name="PIN">PIN received from the authorization site</param>
  93:         /// <param name="AccessToken">The access token</param>
  94:         /// <param name="AccessTokenSecret">The access token secret</param>
  95:         public void GetAccessToken(string PIN, out string AccessToken, out string AccessTokenSecret)
  96:         {
  97:             this.Url = new Uri("https://twitter.com/oauth/access_token");
  98:             this.AddParameter("oauth_verifier", PIN);
  99:             this.Method = HTTPMethod.POST;
 100:             REST.REST RestHelper = new Utilities.Web.REST.REST();
 101:             RestHelper.Url = new Uri(GenerateRequest());
 102:             string Value = RestHelper.POST();
 103:             Regex TokenRegex = new Regex("oauth_token=(?<Value>[^&]*)");
 104:             Match TempToken = TokenRegex.Match(Value);
 105:             AccessToken = TempToken.Groups["Value"].Value;
 106:             Regex TokenSecretRegex = new Regex("oauth_token_secret=(?<Value>[^&]*)");
 107:             Match TempTokenSecret = TokenSecretRegex.Match(Value);
 108:             AccessTokenSecret = TempTokenSecret.Groups["Value"].Value;
 109:         }
 110:  
 111:         /// <summary>
 112:         /// Updates the status of the user
 113:         /// </summary>
 114:         /// <param name="Status">Status of the user (needs to be within 140 characters)</param>
 115:         /// <returns>The XML doc returned from the Twitter service</returns>
 116:         public string UpdateStatus(string Status)
 117:         {
 118:             this.Method = HTTPMethod.POST;
 119:             this.Url = new Uri("http://twitter.com/statuses/update.xml");
 120:             this.AddParameter("status", Status);
 121:             REST.REST RestHelper = new Utilities.Web.REST.REST();
 122:             RestHelper.Url = new Uri(GenerateRequest());
 123:             return RestHelper.POST();
 124:         }
 125:  
 126:         /// <summary>
 127:         /// Gets a user's timeline from the Twitter service
 128:         /// </summary>
 129:         /// <param name="UserName">The screen name of the user</param>
 130:         /// <returns>The XML doc returned from the Twitter service
 131:         /// contatining the timeline</returns>
 132:         public string GetTimeline(string UserName)
 133:         {
 134:             this.Method = HTTPMethod.POST;
 135:             this.Url = new Uri("http://twitter.com/statuses/user_timeline.xml");
 136:             this.AddParameter("screen_name", UserName);
 137:             REST.REST RestHelper = new Utilities.Web.REST.REST();
 138:             RestHelper.Url = new Uri(GenerateRequest());
 139:             return RestHelper.GET();
 140:         }
 141:  
 142:         #endregion
 143:     }
 144: }

The code above uses the OAuth helper class (modified, from the post that I link to so go to the svn store on codeplex to see the latest version) along with a new REST helper class that I created to work with Twitter. But before I show the REST class, I should explain the code above. There are a number of functions here that you need to call prior to ever being able to send anything to Twitter. The first is GetRequestToken. This does what it sounds like. It gets a request token from the Twitter service. The request token is then used to register an app with a user. Prior to authorizing the app, you can't do a damn thing when using OAuth. The request token/token secret that is returned by the Twitter service are then used in the GetAuthorizationSite function. This function simply gets you the location that the user must go in order to authorize your app to work with their account. The user goes to that location, clicks on Allow and is then given a PIN number. Note that during all of this you have to hold onto the request token/secret token so that you can use them along with the PIN in the next function: GetAccessToken. This function actually trades in the request token that you're using for an access token/token secret. These are then used in all of the functions that actually do anything...

That's right, you just went through three steps just so you can start to use the service. It's annoying but on the bright side, it's much more secure. Not to mention it tends to make those who are paranoid trust you a bit more as they are no longer giving you their user name and password. Anyway, the other two functions in the class do two things. The first is a status update function. It's basically what I showed you before in the previous post but cleaned up and using OAuth instead of basic authentication. The second function allows you to query Twitter for a timeline of a single user. It returns an XML file that you'll need to parse, but it does get the job done for now. They should be fairly simple to follow... Well, they would be if I had shown you the REST class before now. So you're not completely confused here it is:

   1: /*
   2: Copyright (c) 2010 <a href="http://www.gutgames.com">James Craig</a>
   3: 
   4: Permission is hereby granted, free of charge, to any person obtaining a copy
   5: of this software and associated documentation files (the "Software"), to deal
   6: in the Software without restriction, including without limitation the rights
   7: to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   8: copies of the Software, and to permit persons to whom the Software is
   9: furnished to do so, subject to the following conditions:
  10: 
  11: The above copyright notice and this permission notice shall be included in
  12: all copies or substantial portions of the Software.
  13: 
  14: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16: FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17: AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18: LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20: THE SOFTWARE.*/
  21:  
  22: #region Usings
  23: using System;
  24: using System.IO;
  25: using System.Net;
  26: using System.Text;
  27: #endregion
  28:  
  29: namespace Utilities.Web.REST
  30: {
  31:     /// <summary>
  32:     /// Class designed to help with calling REST based applications
  33:     /// </summary>
  34:     public class REST
  35:     {
  36:         #region Constructor
  37:  
  38:         /// <summary>
  39:         /// Constructor
  40:         /// </summary>
  41:         public REST()
  42:         {
  43:         }
  44:  
  45:         #endregion
  46:  
  47:         #region Public Functions
  48:  
  49:         /// <summary>
  50:         /// Does a GET to the REST service
  51:         /// </summary>
  52:         /// <returns>a string containing the data returned by the service</returns>
  53:         public string GET()
  54:         {
  55:             HttpWebRequest Request = WebRequest.Create(Url) as HttpWebRequest;
  56:             Request.Method = "GET";
  57:             Request.ContentType = "text/xml";
  58:             SetupData(Request);
  59:             SetupCredentials(Request);
  60:             return SendRequest(Request);
  61:         }
  62:  
  63:         /// <summary>
  64:         /// Does a POST to the REST service
  65:         /// </summary>
  66:         /// <returns>a string containing the data returned by the service</returns>
  67:         public string POST()
  68:         {
  69:             HttpWebRequest Request = WebRequest.Create(Url) as HttpWebRequest;
  70:             Request.Method = "POST";
  71:             Request.ContentType = "application/x-www-form-urlencoded";
  72:             SetupData(Request);
  73:             SetupCredentials(Request);
  74:             return SendRequest(Request);
  75:         }
  76:  
  77:         /// <summary>
  78:         /// Does a DELETE on the REST service
  79:         /// </summary>
  80:         /// <returns>a string containing the data returned by the service</returns>
  81:         public string DELETE()
  82:         {
  83:             HttpWebRequest Request = WebRequest.Create(Url) as HttpWebRequest;
  84:             Request.Method = "DELETE";
  85:             Request.ContentType = "application/x-www-form-urlencoded";
  86:             SetupData(Request);
  87:             SetupCredentials(Request);
  88:             return SendRequest(Request);
  89:         }
  90:  
  91:         /// <summary>
  92:         /// Does a PUT on the REST service
  93:         /// </summary>
  94:         /// <returns>a string containing the data returned by the service</returns>
  95:         public string PUT()
  96:         {
  97:             HttpWebRequest Request = WebRequest.Create(Url) as HttpWebRequest;
  98:             Request.Method = "PUT";
  99:             Request.ContentType = "application/x-www-form-urlencoded";
 100:             SetupData(Request);
 101:             SetupCredentials(Request);
 102:             return SendRequest(Request);
 103:         }
 104:  
 105:         #endregion
 106:  
 107:         #region Private Functions
 108:  
 109:         /// <summary>
 110:         /// Sets up any data that needs to be sent
 111:         /// </summary>
 112:         /// <param name="Request">The web request object</param>
 113:         private void SetupData(HttpWebRequest Request)
 114:         {
 115:             if (string.IsNullOrEmpty(Data))
 116:             {
 117:                 Request.ContentLength = 0;
 118:                 return;
 119:             }
 120:             byte[] ByteData = UTF8Encoding.UTF8.GetBytes(Data);
 121:             Request.ContentLength = ByteData.Length;
 122:             using (Stream RequestStream = Request.GetRequestStream())
 123:             {
 124:                 RequestStream.Write(ByteData, 0, ByteData.Length);
 125:             }
 126:         }
 127:  
 128:         /// <summary>
 129:         /// Sets up any credentials (basic authentication,
 130:         /// for OAuth, please use the OAuth class to create the
 131:         /// URL)
 132:         /// </summary>
 133:         /// <param name="Request">The web request object</param>
 134:         private void SetupCredentials(HttpWebRequest Request)
 135:         {
 136:             if (!string.IsNullOrEmpty(UserName) && !string.IsNullOrEmpty(Password))
 137:             {
 138:                 Request.Credentials = new NetworkCredential(UserName, Password);
 139:             }
 140:         }
 141:  
 142:         /// <summary>
 143:         /// Sends the request to the URL specified
 144:         /// </summary>
 145:         /// <param name="Request">The web request object</param>
 146:         /// <returns>The string returned by the service</returns>
 147:         private string SendRequest(HttpWebRequest Request)
 148:         {
 149:             using (HttpWebResponse Response = Request.GetResponse() as HttpWebResponse)
 150:             {
 151:                 if (Response.StatusCode != HttpStatusCode.OK)
 152:                     throw new Exception("The request did not complete successfully and returned status code " + Response.StatusCode);
 153:                 using (StreamReader Reader = new StreamReader(Response.GetResponseStream()))
 154:                 {
 155:                     return Reader.ReadToEnd();
 156:                 }
 157:             }
 158:         }
 159:  
 160:         #endregion
 161:  
 162:         #region Public Properties
 163:  
 164:         /// <summary>
 165:         /// URL to send the request to
 166:         /// </summary>
 167:         public Uri Url { get; set; }
 168:  
 169:         /// <summary>
 170:         /// Any data that needs to be appended to the request
 171:         /// </summary>
 172:         public string Data { get; set; }
 173:  
 174:         /// <summary>
 175:         /// User name (basic authentication)
 176:         /// </summary>
 177:         public string UserName { get; set; }
 178:  
 179:         /// <summary>
 180:         /// Password (basic authentication
 181:         /// </summary>
 182:         public string Password { get; set; }
 183:  
 184:         #endregion
 185:     }
 186: }

If you've ever dealt with a REST API before, it's pretty basic. But more or less it's divided into four functions, GET, PUT, POST, DELETE. It also allows for basic authentication if you need it. But basically you just load up the URL, any data that needs to be appended, user name and password and call the appropriate function. It in turn returns anything that the server sends back. So there you go, basic REST helper and Twitter helper classes. It's not that difficult to build off of them, so take a look, leave feedback, and happy coding.



Comments

Jero
October 01, 2010 3:31 AM

Hi!thanks!! now I understand the way it works. But now I have a problem. When my program tries to get the request token, an exception occurs: "Error in remote server: 401 not authorized".The error trace is :Temp.GetRequestToken(out Token, out TokenSecret); --> string Value = RestHelper.GET(); --> SendRequest(HttpWebRequest Request) --> HttpWebResponse Response = Request.GetResponse() as HttpWebResponseWhen attempting to get the HttpWebResponse, the exception is launched. I've registered my app, the key and secretKey are OK. I don't know what's happening.Another cuestion: This process needs http, or https? Thanks a lot!

James Craig
September 30, 2010 12:01 PM

Well before you get to any code, you have to register your app with Twitter at: http://dev.twitter.com.Once you've done that, go into the information on their site for your app and pull off the consumer key and consumer key secret. When you have that you have to register your app with your account, this is done in a couple of steps.1) Get the Request TokenThis is done, with the following code:Twitter Temp = new Twitter();Temp.ConsumerKey="YOUR CONSUMER KEY";Temp.ConsumerKeySecret="YOUR CONSUMER SECRET";string Token="";string TokenSecret="";Temp.GetRequestToken(out Token, out TokenSecret);Temp.Token = Token;Temp.TokenSecret = TokenSecret;2) Store the Token/TokenSecret for later. You have to make the next couple of requests with it (store it in the session or user profile or something for web requests).3) Redirect (or

Jero
September 30, 2010 6:46 AM

Hi, nice post, I'm searching for a long time for an example like this. But I can't make a simple Publish action to twitter, I'm not sure the methods that I've to call to make publishing.Please, can you post some example of execution code? Thank you.