diff --git a/Pleroma.Test.sln b/Pleroma.Test.sln
index ffa733b..0195110 100644
--- a/Pleroma.Test.sln
+++ b/Pleroma.Test.sln
@@ -7,8 +7,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pleroma.Test", "Pleroma.Tes
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pleroma", "Pleroma\Pleroma.csproj", "{9E897444-04AE-4063-9945-9964F502994F}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HTTP", "HTTP\HTTP.csproj", "{7F622DB8-062D-4D21-8419-F3E5C7DAEC79}"
-EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -23,10 +21,6 @@ Global
{9E897444-04AE-4063-9945-9964F502994F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9E897444-04AE-4063-9945-9964F502994F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9E897444-04AE-4063-9945-9964F502994F}.Release|Any CPU.Build.0 = Release|Any CPU
- {7F622DB8-062D-4D21-8419-F3E5C7DAEC79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {7F622DB8-062D-4D21-8419-F3E5C7DAEC79}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {7F622DB8-062D-4D21-8419-F3E5C7DAEC79}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {7F622DB8-062D-4D21-8419-F3E5C7DAEC79}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Pleroma.Test/Program.cs b/Pleroma.Test/Program.cs
index 1e93413..8bbc48e 100644
--- a/Pleroma.Test/Program.cs
+++ b/Pleroma.Test/Program.cs
@@ -12,13 +12,7 @@ static class Program
static async Task MainInline()
{
- Pleroma client = new Pleroma("localhost", "Bearer abcdefghijklmnopqrstuvwxyz");
- Account account = await client.GetAccount();
- Status[] statuses = await client.GetTimeline();
-
- Console.WriteLine($"Account: {account} ({account.ID})");
- Console.WriteLine("Public statuses:");
- foreach (Status status in statuses)
+ static void PrintStatus(Status status)
{
Console.Write('\t');
Console.Write(status.CreatedAt.ToShortDateString());
@@ -27,6 +21,30 @@ static class Program
Console.Write(' ');
Console.WriteLine(status);
}
+
+ Pleroma client = new Pleroma("localhost", "Bearer abcdefghijklmnopqrstuvwxyz");
+ Account account = await client.GetAccount();
+ Status[] statuses = await client.GetTimeline();
+
+ Console.WriteLine($"Account: {account} ({account.ID})");
+ Console.WriteLine("Public statuses:");
+ foreach (Status status in statuses)
+ PrintStatus(status);
+
+ Console.WriteLine();
+
+ SearchResults search = await client.Search("bepis", type: SearchType.Statuses, limit: 10);
+ if (search.Statuses == null || search.Statuses.Length == 0)
+ {
+ Console.WriteLine("No search results");
+ }
+ else
+ {
+ Console.WriteLine("Search results for \"bepis\":");
+ foreach (Status status in search.Statuses)
+ PrintStatus(status);
+ }
+
Console.ReadKey();
}
}
diff --git a/Pleroma/API/PublishStatus.cs b/Pleroma/API/PublishStatus.cs
deleted file mode 100644
index 18926c7..0000000
--- a/Pleroma/API/PublishStatus.cs
+++ /dev/null
@@ -1,110 +0,0 @@
-namespace Uwaa.Pleroma.API;
-
-[JsonConverter(typeof(PublishStatusConverter))]
-public class PublishStatus
-{
- ///
- /// Text content of the status.
- ///
- public string Content { get; set; }
-
- ///
- /// Mark status and attached media as sensitive?
- ///
- public bool Sensitive { get; set; }
-
- ///
- /// The number of seconds the posted activity should expire in. When a posted activity expires it will be deleted from the server, and a delete request for it will be federated. This needs to be longer than an hour.
- ///
- public int? ExpiresIn { get; set; } = null!;
-
- ///
- /// ID of the status being replied to, if status is a reply
- ///
- public string? ReplyTo { get; set; }
-
- ///
- /// ID of the status being quoted.
- ///
- public string? Quoting { get; set; }
-
- ///
- /// ISO 639 language code for this status.
- ///
- public string? Language { get; set; }
-
- ///
- /// Visibility of the posted status.
- ///
- public StatusVisibility Visibility { get; set; } = StatusVisibility.Public;
-
- ///
- /// Called after the status has been successfully published.
- ///
- [JsonIgnore]
- public Action? OnPublish;
-
- public PublishStatus(string content)
- {
- Content = content ?? throw new ArgumentNullException(nameof(content));
- }
-}
-
-class PublishStatusConverter : JsonConverter
-{
- public sealed override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(PublishStatus);
-
- public override PublishStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- throw new NotImplementedException();
- }
-
- public override void Write(Utf8JsonWriter writer, PublishStatus status, JsonSerializerOptions options)
- {
- writer.WriteStartObject();
-
- writer.WritePropertyName("status");
- writer.WriteStringValue(status.Content);
-
- writer.WritePropertyName("content-type");
- writer.WriteStringValue("text/plain");
-
- if (status.Sensitive)
- {
- writer.WritePropertyName("sensitive");
- writer.WriteBooleanValue(true);
- }
-
- if (status.ExpiresIn.HasValue)
- {
- writer.WritePropertyName("expires_in");
- writer.WriteNumberValue(status.ExpiresIn.Value);
- }
-
- if (status.ReplyTo != null)
- {
- writer.WritePropertyName("in_reply_to_id");
- writer.WriteStringValue(status.ReplyTo);
- }
-
- if (status.Quoting != null)
- {
- writer.WritePropertyName("quote_id");
- writer.WriteStringValue(status.Quoting);
- }
-
- if (status.Language != null)
- {
- writer.WritePropertyName("language");
- writer.WriteStringValue(status.Language);
- }
-
- if (status.Visibility != StatusVisibility.Public)
- {
- writer.WritePropertyName("visibility");
- writer.WriteStringValue(status.Visibility.ToString().ToLowerInvariant());
- }
-
- writer.WriteEndObject();
- }
-}
\ No newline at end of file
diff --git a/Pleroma/Pleroma.cs b/Pleroma/Pleroma.cs
index 081a7dd..669c167 100644
--- a/Pleroma/Pleroma.cs
+++ b/Pleroma/Pleroma.cs
@@ -1,8 +1,11 @@
-using Uwaa.HTTP;
-using Uwaa.Pleroma.API;
+using System.Net;
+using System.Net.Http.Headers;
+using System.Text;
namespace Uwaa.Pleroma;
+delegate void AddPair(string key, string value);
+
///
/// A pleroma client.
///
@@ -11,101 +14,63 @@ public class Pleroma
static readonly JsonSerializerOptions SerializerOptions = new()
{
PropertyNameCaseInsensitive = true,
- NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString,
+ NumberHandling = JsonNumberHandling.AllowReadingFromString,
};
- static readonly MIMEType JsonMIMEType = new("application", "json");
+ static string CreateQuery(Action generator)
+ {
+ StringBuilder sb = new StringBuilder();
+ void addPair(string key, string value)
+ {
+ if (sb.Length == 0)
+ sb.Append('?');
+ else
+ sb.Append('&');
+ sb.Append(WebUtility.UrlEncode(key));
+ sb.Append('=');
+ sb.Append(WebUtility.UrlEncode(value));
+ }
+ generator(addPair);
+ return sb.ToString();
+ }
- ///
- /// The hostname of the pleroma instance.
- ///
- public string Host;
-
- ///
- /// The full token, including the "Bearer" string.
- ///
- public string Authorization;
-
///
/// The user agent string.
///
public string UserAgent = "Uwaa.Pleroma/0.0";
- public Pleroma(string host, string authorization)
+ public HttpClient HttpClient;
+
+ public Pleroma(string host, string? authorization)
{
- Host = host;
- Authorization = authorization;
+ UriBuilder builder = new UriBuilder();
+ builder.Scheme = "https";
+ builder.Host = host;
+
+ HttpClient = new HttpClient();
+ HttpClient.DefaultRequestHeaders.Add("User-agent", UserAgent);
+ HttpClient.BaseAddress = builder.Uri;
+ HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
+ if (authorization != null)
+ HttpClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(authorization);
}
- async Task RequestJSON(HttpRequest req)
+ async Task Retry(HttpRequestMessage req)
{
- req.Fields.UserAgent = UserAgent;
- req.Fields.Authorization = Authorization;
while (true)
{
- HttpResponse res = await HttpClient.Request(Host, true, req);
+ HttpResponseMessage res = await HttpClient.SendAsync(req);
- if (res.StatusCode == 404)
- return;
+ if (res.StatusCode == HttpStatusCode.NotFound)
+ return default;
- if (res.Content.HasValue)
- {
- if (res.Fields.ContentType.HasValue && !res.Fields.ContentType.Value.Match(JsonMIMEType))
- throw new HttpException(res.Content.Value.AsText);
+ if (res.Content == null)
+ throw new HttpRequestException("Server responded with no content");
- if (res.StatusCode is >= 400 and < 600)
- {
- try
- {
- string text = res.Content.Value.AsText;
- PleromaException? err = JsonSerializer.Deserialize(text, SerializerOptions);
- if (err != null && err.Text != null)
- {
- if (err.Text == "Throttled")
- {
- await Task.Delay(5000);
- continue; //Retry
- }
- else
- {
- throw err;
- }
- }
- }
- catch (JsonException)
- {
- //Not an error
- }
- }
- }
+ string text = await res.Content.ReadAsStringAsync();
- if (res.StatusCode is not >= 200 or not < 300)
- throw new HttpException("Unknown error occurred");
- }
- }
-
- async Task RequestJSON(HttpRequest req) where T : class
- {
- req.Fields.UserAgent = UserAgent;
- req.Fields.Authorization = Authorization;
- req.Fields.Accept = [JsonMIMEType];
- while (true)
- {
- HttpResponse res = await HttpClient.Request(Host, true, req);
-
- if (res.StatusCode == 404)
- return null;
-
- if (!res.Fields.ContentType.HasValue || !res.Fields.ContentType.Value.Match(JsonMIMEType))
- throw new HttpException("Server did not respond with JSON" + (res.Content.HasValue ? ", got: " + res.Content.Value.AsText : null));
-
- if (!res.Content.HasValue)
- throw new HttpException("Server responded with no content");
-
- string text = res.Content.Value.AsText;
-
- if (res.StatusCode is >= 400 and < 600)
+ if (res.StatusCode is >= (HttpStatusCode)400 and < (HttpStatusCode)600)
{
try
{
@@ -129,10 +94,10 @@ public class Pleroma
}
}
- if (res.StatusCode is >= 200 and < 300)
- return JsonSerializer.Deserialize(text, SerializerOptions) ?? throw new HttpException("Couldn't deserialize response");
+ if (res.StatusCode is >= (HttpStatusCode)200 and < (HttpStatusCode)300)
+ return JsonSerializer.Deserialize(text, SerializerOptions) ?? throw new HttpRequestException("Couldn't deserialize response");
else
- throw new HttpException("Unknown error occurred");
+ throw new HttpRequestException("Unknown error occurred");
}
}
@@ -140,16 +105,74 @@ public class Pleroma
/// Posts a status.
///
/// The status data to send, including the content, visibility, etc.
- /// Thrown if something goes wrong while uploading the status.
+ /// Thrown if something goes wrong while uploading the status.
/// The status if posting was successful.
- public async Task PostStatus(PublishStatus status)
+ public Task PostStatus(string content,
+ bool sensitive = false,
+ int? expiresIn = null,
+ string? replyTo = null,
+ string? quoting = null,
+ string? language = null,
+ StatusVisibility visibility = StatusVisibility.Public)
{
- HttpRequest req = new HttpRequest(HttpMethod.POST, "/api/v1/statuses");
- req.Content = new HttpContent(JsonMIMEType, JsonSerializer.SerializeToUtf8Bytes(status, SerializerOptions));
- req.Fields.Accept = [JsonMIMEType];
- Status result = (await RequestJSON(req))!;
- status.OnPublish?.Invoke();
- return result;
+ MemoryStream mem = new MemoryStream();
+
+ {
+ Utf8JsonWriter writer = new Utf8JsonWriter(mem, new JsonWriterOptions() { SkipValidation = true });
+ writer.WriteStartObject();
+
+ writer.WritePropertyName("status");
+ writer.WriteStringValue(content);
+
+ writer.WritePropertyName("content-type");
+ writer.WriteStringValue("text/plain");
+
+ if (sensitive)
+ {
+ writer.WritePropertyName("sensitive");
+ writer.WriteBooleanValue(true);
+ }
+
+ if (expiresIn.HasValue)
+ {
+ writer.WritePropertyName("expires_in");
+ writer.WriteNumberValue(expiresIn.Value);
+ }
+
+ if (replyTo != null)
+ {
+ writer.WritePropertyName("in_reply_to_id");
+ writer.WriteStringValue(replyTo);
+ }
+
+ if (quoting != null)
+ {
+ writer.WritePropertyName("quote_id");
+ writer.WriteStringValue(quoting);
+ }
+
+ if (language != null)
+ {
+ writer.WritePropertyName("language");
+ writer.WriteStringValue(language);
+ }
+
+ if (visibility != StatusVisibility.Public)
+ {
+ writer.WritePropertyName("visibility");
+ writer.WriteStringValue(visibility.ToString().ToLowerInvariant());
+ }
+
+ writer.WriteEndObject();
+ writer.Flush();
+ }
+
+ mem.Position = 0;
+ HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, "/api/v1/statuses");
+ req.Content = new StreamContent(mem);
+ req.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
+
+ return Retry(req)!;
}
///
@@ -158,11 +181,11 @@ public class Pleroma
public Task GetTimeline()
{
//TODO: Parameters and selecting different timelines (home, public, bubble)
- HttpRequest req = new HttpRequest(HttpMethod.GET, "/api/v1/timelines/public");
- req.Fields.Accept = [ JsonMIMEType ];
- return RequestJSON(req)!;
+
+ return Retry(new HttpRequestMessage(HttpMethod.Get, "/api/v1/timelines/public"))!;
}
+
///
/// Fetches the latest statuses from a user's timeline.
///
@@ -174,9 +197,7 @@ public class Pleroma
///
public Task GetTimeline(string account_id)
{
- HttpRequest req = new HttpRequest(HttpMethod.GET, $"/api/v1/accounts/{account_id}/statuses");
- req.Fields.Accept = [JsonMIMEType];
- return RequestJSON(req)!;
+ return Retry(new HttpRequestMessage(HttpMethod.Get, $"/api/v1/accounts/{account_id}/statuses"))!;
}
///
@@ -184,9 +205,7 @@ public class Pleroma
///
public Task GetAccount()
{
- HttpRequest req = new HttpRequest(HttpMethod.GET, "/api/v1/accounts/verify_credentials");
- req.Fields.Accept = [JsonMIMEType];
- return RequestJSON(req)!;
+ return Retry(new HttpRequestMessage(HttpMethod.Get, "/api/v1/accounts/verify_credentials"))!;
}
///
@@ -194,9 +213,7 @@ public class Pleroma
///
public Task GetAccount(string id)
{
- HttpRequest req = new HttpRequest(HttpMethod.GET, $"/api/v1/accounts/{id}");
- req.Fields.Accept = [JsonMIMEType];
- return RequestJSON(req);
+ return Retry(new HttpRequestMessage(HttpMethod.Get, $"/api/v1/accounts/{id}"));
}
///
@@ -210,9 +227,7 @@ public class Pleroma
///
public Task GetContext(string status_id)
{
- HttpRequest req = new HttpRequest(HttpMethod.GET, $"/api/v1/statuses/{status_id}/context");
- req.Fields.Accept = [JsonMIMEType];
- return RequestJSON(req);
+ return Retry(new HttpRequestMessage(HttpMethod.Get, $"/api/v1/statuses/{status_id}/context"));
}
///
@@ -220,9 +235,7 @@ public class Pleroma
///
public Task GetStatus(string status_id)
{
- HttpRequest req = new HttpRequest(HttpMethod.GET, $"/api/v1/statuses/{status_id}");
- req.Fields.Accept = [JsonMIMEType];
- return RequestJSON(req);
+ return Retry(new HttpRequestMessage(HttpMethod.Get, $"/api/v1/statuses/{status_id}"));
}
///
@@ -236,9 +249,7 @@ public class Pleroma
///
public Task Delete(string status_id)
{
- HttpRequest req = new HttpRequest(HttpMethod.DELETE, $"/api/v1/statuses/{status_id}");
- req.Fields.Accept = [JsonMIMEType];
- return RequestJSON(req);
+ return Retry