Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Twitter POST problems using api 1.1

We've just changed to Twitter api 1.1, and now Tweeting doesn't work & returns an error "The remote server returned an error: (400) Bad Request." Researching on SO about this suggests that it's something to do with authentication, but we are sending the accessToken & secret which we've just got from the login page. It all worked fine with api 1.0. The code is -

    public void Tweet(Action<string> response, string message)
    {
        StringBuilder sb = new StringBuilder();
        sb.Append("POST&");
        sb.Append(Uri.EscapeDataString(_postUrl));
        sb.Append("&");

        string oauthNonce = Convert.ToBase64String(new ASCIIEncoding().GetBytes(DateTime.Now.Ticks.ToString()));
        string timeStamp = MakeTimestamp();

        var dict = new SortedDictionary<string, string>
        {
            { "oauth_consumer_key", _oAuthConfig.ConsumerKey },
            { "oauth_nonce", oauthNonce },
            { "oauth_signature_method", "HMAC-SHA1" },
            { "oauth_timestamp", timeStamp },
            { "oauth_token", _accessToken },
            { "oauth_version", "1.0" },
        };

        foreach (var keyValuePair in dict)
        {
            sb.Append(Uri.EscapeDataString(string.Format("{0}={1}&", keyValuePair.Key, keyValuePair.Value)));
        }

        string encodedMessage = EscapeAdditionalChars(Uri.EscapeDataString(message));
        sb.Append(Uri.EscapeDataString("status=" + encodedMessage));

        string signatureBaseString = sb.ToString();


        // create the signature

        string signatureKey = Uri.EscapeDataString(_oAuthConfig.ConsumerSecret) + "&" + Uri.EscapeDataString(_accessTokenSecret);

        var hmacsha1 = new HMACSHA1(new ASCIIEncoding().GetBytes(signatureKey));

        string signatureString = Convert.ToBase64String(hmacsha1.ComputeHash(new ASCIIEncoding().GetBytes(signatureBaseString)));


        // create the headers

        string authorizationHeaderParams = String.Empty;

        authorizationHeaderParams += "OAuth ";
        authorizationHeaderParams += "oauth_consumer_key=\"" + _oAuthConfig.ConsumerKey + "\", ";
        authorizationHeaderParams += "oauth_nonce=\"" + oauthNonce + "\", ";
        authorizationHeaderParams += "oauth_signature=\"" + Uri.EscapeDataString(signatureString) + "\", ";
        authorizationHeaderParams += "oauth_signature_method=\"" + "HMAC-SHA1" + "\", ";
        authorizationHeaderParams += "oauth_timestamp=\"" + timeStamp + "\", ";
        authorizationHeaderParams += "oauth_token=\"" + _accessToken + "\", ";
        authorizationHeaderParams += "oauth_version=\"" + "1.0" + "\"";

        string messageToPost = EscapeAdditionalChars(SpacesToPlusSigns(message));


        // initialise the WebClient

        WebClient client = new WebClient();

        client.Headers [HttpRequestHeader.Authorization] = authorizationHeaderParams;

        client.UploadDataCompleted += (s, eArgs) =>
        {
            if (eArgs.Error == null)
                response(DefaultSuccessMessage());
            else
                response(eArgs.Error.Message);
        };

        try
        {
            Uri uri = new Uri(_postUrl);
            try
            {
                client.UploadDataAsync(uri, "POST", Encoding.UTF8.GetBytes("status=" + messageToPost));
            }
            catch (WebException e)
            {
                Log.Info("TwitterService->Tweet web error: " + e.Message);
                response(DefaultErrorMessage());
            }
            catch (Exception e)
            {
                // Can happen if we had already favorited this status
                Log.Info("TwitterService->Tweet error: " + e.Message);
                response(DefaultErrorMessage());
            }
        }
        catch (WebException e)
        {
            Log.Info("TwitterService->Tweet web error 2: " + e.Message);
            response(DefaultErrorMessage());
        }
        catch (Exception e)
        {
            Log.Info("TwitterService->Tweet error 2: " + e.Message);
            response(DefaultErrorMessage());
        }
    }

Basically, I'd like to be able to Tweet without using any 3rd party libraries such as Twitterizer (even TweetStation seems to be broken with api 1.1) - surely it can't be that difficult!

Any help much appreciated, as it feels a bit like a brick wall at the moment - I'm also fairly new to c#, which doesn't help...

Edited to show code which wasn't clear previously.

like image 871
SomaMan Avatar asked Mar 24 '23 11:03

SomaMan


2 Answers

Finally found the solution, as usual with most of these things, it was pretty simple. Code below -

    public void Tweet(Action<string> response, string message)
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendFormat ("status={0}", PercentEncode(message));

        string content = sb.ToString();


        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_postUrl);

        request.Headers.Add("Authorization", AuthorizeRequest(_accessToken, _accessTokenSecret, "POST", new Uri(_postUrl), content));
        request.ContentType = "application/x-www-form-urlencoded";
        request.ServicePoint.Expect100Continue = false;
        request.Method = "POST";


        try
        {
            try
            {
                using (Stream stream = request.GetRequestStream())
                {
                    Byte[] streamContent = Encoding.UTF8.GetBytes("status=" + PercentEncode(message));
                    stream.Write(streamContent, 0, streamContent.Length);
                }


                HttpWebResponse webResponse = (HttpWebResponse)request.GetResponse();

                string contents = "";
                using (Stream stream = webResponse.GetResponseStream())
                    using (StreamReader reader = new StreamReader(stream))
                    {
                        contents = reader.ReadToEnd();
                    }

                Console.WriteLine("Twitter response: " + contents);

                response(DefaultSuccessMessage());

            }
            catch (WebException e)
            {
                Log.Info("TwitterService->Tweet web error: " + e.Message);
                response(DefaultErrorMessage());
            }
            catch (Exception e)
            {
                // Can happen if we had already favorited this status
                Log.Info("TwitterService->Tweet error: " + e.Message);
                response(DefaultErrorMessage());
            }
        }
        catch (WebException e)
        {
            Log.Info("TwitterService->Tweet web error 2: " + e.Message);
            response(DefaultErrorMessage());
        }
        catch (Exception e)
        {
            Log.Info("TwitterService->Tweet error 2: " + e.Message);
            response(DefaultErrorMessage());
        }
    }


    private string AuthorizeRequest(string oauthToken, string oauthTokenSecret, string method, Uri uri, string data)
    {
        string oauthNonce = Convert.ToBase64String(new ASCIIEncoding().GetBytes(DateTime.Now.Ticks.ToString()));

        var headers = new Dictionary<string, string>()
        {
            { "oauth_consumer_key", _oAuthConfig.ConsumerKey },
            { "oauth_nonce", oauthNonce },
            { "oauth_signature_method", "HMAC-SHA1" },
            { "oauth_timestamp", MakeTimestamp() },
            { "oauth_token", oauthToken },
            { "oauth_verifier", PercentEncode(_authorizationVerifier) },
            { "oauth_version", "1.0A" }
        };
        var signatureHeaders = new Dictionary<string,string>(headers);

        // Add the data and URL query string to the copy of the headers for computing the signature
        if (data != null && data != "")
        {
            var parsed = HttpUtility.ParseQueryString(data);
            foreach (string k in parsed.Keys)
            {
                signatureHeaders.Add(k, PercentEncode(parsed [k]));
            }
        }

        var nvc = HttpUtility.ParseQueryString(uri.Query);
        foreach (string key in nvc)
        {
            if (key != null)
                signatureHeaders.Add(key, PercentEncode(nvc [key]));
        }

        string signature = MakeSignature (method, uri.GetLeftPart(UriPartial.Path), signatureHeaders);
        string compositeSigningKey = MakeSigningKey(_oAuthConfig.ConsumerSecret, oauthTokenSecret);
        string oauth_signature = MakeOAuthSignature(compositeSigningKey, signature);

        headers.Add ("oauth_signature", PercentEncode(oauth_signature));


        return HeadersToOAuth(headers);
    }


    private static string PercentEncode (string s)
    {
        var sb = new StringBuilder ();

        foreach (byte c in Encoding.UTF8.GetBytes (s))
        {
            if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~')
                sb.Append ((char) c);
            else
            {
                sb.AppendFormat ("%{0:X2}", c);
            }
        }
        return sb.ToString ();
    }


    private static string MakeTimestamp ()
    {
        return ((long) (DateTime.UtcNow - _unixBaseTime).TotalSeconds).ToString ();
    }

    private static string MakeSignature (string method, string base_uri, Dictionary<string,string> headers)
    {
        var items = from k in headers.Keys orderby k 
            select k + "%3D" + PercentEncode (headers [k]);

        return method + "&" + PercentEncode (base_uri) + "&" + 
            string.Join ("%26", items.ToArray ());
    }

    private static string MakeSigningKey (string consumerSecret, string oauthTokenSecret)
    {
        return PercentEncode (consumerSecret) + "&" + (oauthTokenSecret != null ? PercentEncode (oauthTokenSecret) : "");
    }

    private static string MakeOAuthSignature (string compositeSigningKey, string signatureBase)
    {
        var sha1 = new HMACSHA1 (Encoding.UTF8.GetBytes (compositeSigningKey));

        return Convert.ToBase64String (sha1.ComputeHash (Encoding.UTF8.GetBytes (signatureBase)));
    }

    private static string HeadersToOAuth (Dictionary<string,string> headers)
    {
        return "OAuth " + String.Join (",", (from x in headers.Keys select String.Format ("{0}=\"{1}\"", x, headers [x])).ToArray ());
    }

With Twitter api 1.0, I used a WebClient to post, that doesn't work with api 1.1, and it seems that the reason for this is that you can't set the ContentType or the ServicePoint.Expect100Continue properties - without these set as I've set them, the request is sent back as (401) unauthorized. Nothing to do with encoding problems in the end.

Thanks to others for the various helper methods.

like image 179
SomaMan Avatar answered Apr 06 '23 01:04

SomaMan


I had exactly the same problem:

This is exactly what you need to do here:

Authenticate and request a user's timeline with Twitter API 1.1 oAuth

I have created a project for this at : https://github.com/andyhutch77/oAuthTwitterTimeline

It also includes an MVC, Web app and console demo.

like image 44
hutchonoid Avatar answered Apr 05 '23 23:04

hutchonoid