Twitter API

Habe heute eine Weile gebraucht bis ich einen Tweet absetzen konnte. Für mich und im Fall, das es jemand anderer braucht.

Twitter Developer Platform overview | Docs | Twitter Developer Platform

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

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}&{Uri.EscapeDataString(url)}&{Uri.EscapeDataString(sigBaseString)}";

        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

Imports System.Net.Http
Imports System.Security.Cryptography
Imports System.Text
Imports System.Text.Json

Public Class TwitterClient
    Private ReadOnly consumerKey As String
    Private ReadOnly consumerSecret As String
    Private ReadOnly accessToken As String
    Private ReadOnly tokenSecret As String

    Private ReadOnly httpClient As HttpClient

    Public Sub New(httpClient As HttpClient, consumerKey As String, consumerSecret As String, accessToken As String, tokenSecret As String)
        Me.httpClient = httpClient
        Me.consumerKey = consumerKey
        Me.consumerSecret = consumerSecret
        Me.accessToken = accessToken
        Me.tokenSecret = tokenSecret
    End Sub

    Public Async Function PostTweet(text As String) As Task
        Dim timstamp = CreateTimestamp()
        Dim nonce = CreateNonce()
        Dim body = JsonSerializer.Serialize(New With {Key .text = text})
        Dim uri = New Uri("https://api.twitter.com/2/tweets")

        Dim request = New HttpRequestMessage With {
            .RequestUri = uri,
            .Method = HttpMethod.Post,
            .Content = New StringContent(body, Encoding.ASCII, "application/json")
        }

        Dim 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)}""")

        Dim response = Await httpClient.SendAsync(request)

        response.EnsureSuccessStatusCode()
    End Function

    Private Function CreateSignature(url As String, method As String, nonce As String, timestamp As String) As String
        Dim parameters = New Dictionary(Of 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")

        Dim sigBaseString = CombineQueryParams(parameters)

        Dim signatureBaseString = $"{method}&{Uri.EscapeDataString(url)}&{Uri.EscapeDataString(sigBaseString)}"

        Dim compositeKey = $"{Uri.EscapeDataString(consumerSecret)}&{Uri.EscapeDataString(tokenSecret)}"

        Using hasher = New HMACSHA1(Encoding.ASCII.GetBytes(compositeKey))
            Return Convert.ToBase64String(hasher.ComputeHash(Encoding.ASCII.GetBytes(signatureBaseString)))
        End Using
    End Function

    Private Function CreateTimestamp() As String
        Dim totalSeconds = (DateTime.UtcNow - New DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds

        Return Convert.ToInt64(totalSeconds).ToString()
    End Function

    Private Function CreateNonce() As String
        Return Convert.ToBase64String(New ASCIIEncoding().GetBytes(DateTime.Now.Ticks.ToString()))
    End Function

    Public Function CombineQueryParams(parameters As Dictionary(Of String, String)) As String
        Dim sb = New StringBuilder()
        Dim first = True

        For Each param In parameters
            If Not first Then
                sb.Append("&")
            End If

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

            first = False
        Next

        Return sb.ToString()
    End Function
End Class

OpenAI und PDF

Rambo sagte einmal „Das ist eine lange Geschichte“ und war damit eine kurze Zusammenfassung der ganzen Rambo Filme.

Mit Open AI soll das nun möglich sein.
Also los aus nostalgischen Gründen werde ich ein Ajax mit einem Webservice machen – quisi quasi fast ein Micoservice 🙂
Also machen wir mal das Upload.

using Google.Protobuf.WellKnownTypes;
using Org.BouncyCastle.Asn1.Ocsp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceModel.Web;
using System.Web;
using System.Web.Script.Services;
using System.Web.Services;
using System.Web.Services.Description;
using System.Web.UI.WebControls;

namespace GPT3PDF
{
    /// <summary>
    /// Zusammenfassungsbeschreibung für AnaylseService
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    // Wenn der Aufruf dieses Webdiensts aus einem Skript zulässig sein soll, heben Sie mithilfe von ASP.NET AJAX die Kommentarmarkierung für die folgende Zeile auf. 
    [System.Web.Script.Services.ScriptService]
    public class AnaylseService : System.Web.Services.WebService
    {

        [WebMethod]
        public string HelloWorld()
        {
            return "Hello World";
        }

        [WebMethod]
        [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
        [WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json)]
        public PDFResponse Upload()
        {

            HttpFileCollection Files = HttpContext.Current.Request.Files;


            HttpPostedFile File = Files[0];
            byte[] pdfBytes;


            using (var binaryReader = new BinaryReader(File.InputStream))
            {
                pdfBytes = binaryReader.ReadBytes(File.ContentLength);
            }


            try
            {
                PDFAnalysis pDFAnalysis = new PDFAnalysis();
                string extract = pDFAnalysis.AnalyzePDF(pdfBytes);
              
                return new PDFResponse() { Message = extract };
                
            }
            catch (Exception ex)
            {
              //Error do something with it or leave it :)  

            }

            return new PDFResponse() { Message = "Error! " };

        }
    }

    public class PDFResponse
    {
        public string Message { get; set; } 
        
    }
}


Nun müssen wir nur noch den Text aus dem PDF bekommen. Zum Glück müssen wir das Rad nicht neu erfinden und können auf zum Beispiel PdfPig | Read and extract text and other content from PDFs in C# (port of PDFBox) (uglytoad.github.io) verwenden.
Das extrahieren geht ganz einfach:

  private string ExtractTextFromPDF(byte[] file)
        {

            string extractedText;
            using (var pdf = PdfDocument.Open(file))
            {
                var sb = new StringBuilder();
                for (var i = 0; i < pdf.NumberOfPages; i++)
                {
                    var page = pdf.GetPage(i + 1);
                    sb.Append(page.Text);
                }
                extractedText = sb.ToString();

            }
            return extractedText;
        }

Das File übergeben wir als Byte[] damit wir es nicht irgendwo auf dem Server zwischen speichern müssen.


Nostalgisches Upload und Text extrahieren ist doch einfach oder?

So als nächstes kommt der lustige Teil. Der Aufruf des OpenAI Api.

  private string AnalyzeTextWithChatGpt(string extractedText)
        {
            string responseContent = string.Empty;
            try
            {
                List<MessageObj> roles = new List<MessageObj>();
                roles.Add(new MessageObj() { role = "system", content = "Summarize the text" });
                roles.Add(new MessageObj() { role = "user", content = extractedText });
                //MessageObj[] msg;

                // Set up the OpenAI API client
                var content = new StringContent(
            JsonConvert.SerializeObject(new
            {
                model = "gpt-3.5-turbo",
                messages = roles


            }),
            Encoding.UTF8,
            "application/json");

                client.DefaultRequestHeaders.Clear();
                client.DefaultRequestHeaders.Add("Authorization", "Bearer " + chatGptApiKey);
                //var response = client.PostAsync("https://api.openai.com/v1/engines/davinci-codex/completions", content).Result;

                var response = client.PostAsync("https://api.openai.com/v1/chat/completions", content).Result;

                responseContent = response.Content.ReadAsStringAsync().Result;

                var explanation = JsonConvert.DeserializeObject<dynamic>(responseContent).choices[0].message.content;
                return explanation;
            }
            catch (Exception ex)
            {

                Exception exception = new Exception(responseContent, ex);
                throw exception;
            }


        }

Der ganze Source ist unter gpiwonka/GPT3PDF (github.com). Es fehlen natürlich eine gescheite Fehlerbehandlung und auch ein Verfahren, wenn das PDF zu groß ist. Hierzu müsste das PDF gesplittet werden und dann jeder einzelne Teil zusammengefasst werden. Live anzuschauen unter: gpt3pdf20230526121655.azurewebsites.net