Title image of C#  Twitter Api - post tweet oauth 1-0a

C# Twitter Api - post tweet oauth 1-0a

29 October 2022

·
C#

In my recent side project, I collected and calculated stats related to fantasy premier league: https://wallstreetfpl.com. These stats are calculated automatically on a weekly basis and I thought it would be a good idea to also post them on Twitter.

Twitter’s API

Twitter allows creating new tweets through their API entirely for free. All you need to do is create a developer account to generate the necessary keys.

Twitter’s API is authenticated by OAuth and while OAuth is very flexible it can be painful to implement. It’s certainly not as easy as just having an API key…

The endpoint we can use to post new tweets is locked by OAuth 1.0a. It requires 4 keys: ConsumerKey, ConsumerSecret, AccessToken and AccessTokenSecret. Using these keys you also need to create a signature for the request’s header.

The code

It took a while to figure out the code so enjoy!

using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

namespace Twitter
{
    public class TwitterClient
    {
        private readonly string consumerKey;
        private readonly string consumerSecret;
        private readonly string accessToken;
        private readonly string tokenSecret;

        private readonly HttpClient httpClient;

        public TwitterClient(HttpClient httpClient, string consumerKey, string consumerSecret, string accessToken, string tokenSecret)
        {
            this.httpClient = httpClient;
            this.consumerKey = consumerKey;
            this.consumerSecret = consumerSecret;
            this.accessToken = accessToken;
            this.tokenSecret = tokenSecret;
        }

        public async Task PostTweet(string text)
        {
            var timstamp = CreateTimestamp();
            var nonce = CreateNonce();
            var body = JsonSerializer.Serialize(new { 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)}""");

            var response = await httpClient.SendAsync(request);

            response.EnsureSuccessStatusCode();
        }

        private string CreateSignature(string url, string method, string nonce, string timestamp)
        {
            var parameters = new Dictionary<string, string>();

            parameters.Add("oauth_consumer_key", consumerKey);
            parameters.Add("oauth_nonce", nonce);
            parameters.Add("oauth_signature_method", "HMAC-SHA1");
            parameters.Add("oauth_timestamp", timestamp);
            parameters.Add("oauth_token", accessToken);
            parameters.Add("oauth_version", "1.0");

            var sigBaseString = CombineQueryParams(parameters);

            var signatureBaseString =
                method.ToString() + "&" +
                Uri.EscapeDataString(url) + "&" +
                Uri.EscapeDataString(sigBaseString.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)));
            }
        }

        private string CreateTimestamp()
        {
            var totalSeconds = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc))
                .TotalSeconds;

            return Convert.ToInt64(totalSeconds).ToString();
        }

        private string CreateNonce()
        {
            return Convert.ToBase64String(
                new ASCIIEncoding().GetBytes(
                    DateTime.Now.Ticks.ToString()));
        }

        public string CombineQueryParams(Dictionary<string, string> parameters)
        {
            var sb = new StringBuilder();

            var first = true;

            foreach (var param in parameters)
            {
                if (!first)
                {
                    sb.Append("&");
                }

                sb.Append(param.Key);
                sb.Append("=");
                sb.Append(Uri.EscapeDataString(param.Value));

                first = false;
            }

            return sb.ToString();
        }
    }
}