diff --git a/Pleroma/CommonMIMEs.cs b/Pleroma/CommonMIMEs.cs
new file mode 100644
index 0000000..6e35cb3
--- /dev/null
+++ b/Pleroma/CommonMIMEs.cs
@@ -0,0 +1,18 @@
+namespace Uwaa.Pleroma;
+
+public enum CommonMIMEs
+{
+ PNG,
+ JPEG,
+ GIF,
+
+ MP4,
+ WEBM,
+ MOV,
+
+ WAV,
+ MP3,
+ OGG,
+
+ Text,
+}
diff --git a/Pleroma/Models/Attachment.cs b/Pleroma/Models/Attachment.cs
index 62f4ff1..b95b15e 100644
--- a/Pleroma/Models/Attachment.cs
+++ b/Pleroma/Models/Attachment.cs
@@ -49,6 +49,16 @@ public class Attachment
///
[JsonPropertyName("url")]
public string URL { get; set; } = null!;
+
+ ///
+ /// Downloads the attachment.
+ ///
+ public async Task Download()
+ {
+ using HttpClient client = new HttpClient();
+ HttpResponseMessage res = await client.GetAsync(URL);
+ return await res.Content.ReadAsByteArrayAsync();
+ }
}
[JsonConverter(typeof(EnumLowerCaseConverter))]
@@ -76,4 +86,31 @@ public class PleromaAttachmentData
///
[JsonPropertyName("name")]
public string Name { get; set; } = null!;
+}
+
+[JsonConverter(typeof(MediaIDConverter))]
+public readonly struct MediaID(string id)
+{
+ public static implicit operator MediaID(string id) => new MediaID(id);
+
+ public static implicit operator MediaID(Attachment attachment) => new MediaID(attachment.ID);
+
+ public readonly string ID = id;
+
+ public override string ToString() => ID;
+}
+
+class MediaIDConverter : JsonConverter
+{
+ public override bool CanConvert(Type type) => type.IsAssignableTo(typeof(MediaID));
+
+ public override MediaID Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new MediaID(reader.GetString() ?? throw new NullReferenceException("Expected a string, got null"));
+ }
+
+ public override void Write(Utf8JsonWriter writer, MediaID value, JsonSerializerOptions options)
+ {
+ writer.WriteStringValue(value.ID);
+ }
}
\ No newline at end of file
diff --git a/Pleroma/Models/PleromaException.cs b/Pleroma/Models/PleromaException.cs
index 3c13a69..e5d5ada 100644
--- a/Pleroma/Models/PleromaException.cs
+++ b/Pleroma/Models/PleromaException.cs
@@ -7,5 +7,25 @@ public class PleromaException : Exception
public override string Message => Text;
- public override string ToString() => Text;
+ public override string ToString() => Message;
}
+
+public class PleromaAggregateException : Exception
+{
+ [JsonPropertyName("errors")]
+ public PleromaInnerException[] Text { get; set; } = null!;
+
+ public override string Message => string.Join("\n", (object?[])Text);
+
+ public override string ToString() => Message;
+}
+
+public class PleromaInnerException : Exception
+{
+ [JsonPropertyName("detail")]
+ public string Text { get; set; } = null!;
+
+ public override string Message => Text;
+
+ public override string ToString() => Message;
+}
\ No newline at end of file
diff --git a/Pleroma/Pleroma.cs b/Pleroma/Pleroma.cs
index d6dc489..cb8fb65 100644
--- a/Pleroma/Pleroma.cs
+++ b/Pleroma/Pleroma.cs
@@ -65,7 +65,7 @@ public class Pleroma
string text = await res.Content.ReadAsStringAsync();
- if (res.StatusCode is >= (HttpStatusCode)400 and < (HttpStatusCode)600)
+ if (res.StatusCode is >= (HttpStatusCode)300)
{
try
{
@@ -87,6 +87,17 @@ public class Pleroma
{
//Not an error
}
+
+ try
+ {
+ PleromaAggregateException? err = JsonSerializer.Deserialize(text, SerializerOptions);
+ if (err != null && err.Text != null)
+ throw err;
+ }
+ catch (JsonException)
+ {
+ //Not an error
+ }
}
if (res.StatusCode is >= (HttpStatusCode)200 and < (HttpStatusCode)300)
@@ -179,28 +190,36 @@ public class Pleroma
/// ID of the status being replied to, if status is a reply
/// ID of the status being quoted.
/// ISO 639 language code for this status.
+ /// Array of Attachment ids to be attached as media.
/// Visibility of the posted status.
- /// Thrown if something goes wrong while uploading the status.
- /// The status if posting was successful.
- public Task PostStatus(string content,
+ /// Thrown if something goes wrong while publishing the status.
+ /// The newly published status if posting was successful.
+ public Task PostStatus(string? content = null,
bool sensitive = false,
int? expiresIn = null,
string? replyTo = null,
string? quoting = null,
string? language = null,
+ MediaID[]? attachments = null,
StatusVisibility visibility = StatusVisibility.Public)
{
+ if (content == null && (attachments == null || attachments.Length == 0))
+ throw new ArgumentException("Cannot post nothing. Content and/or attachments must be provided.");
+
MemoryStream mem = new MemoryStream();
{
Utf8JsonWriter writer = new Utf8JsonWriter(mem, new JsonWriterOptions() { SkipValidation = true });
writer.WriteStartObject();
- writer.WritePropertyName("status");
- writer.WriteStringValue(content);
+ if (content != null)
+ {
+ writer.WritePropertyName("status");
+ writer.WriteStringValue(content);
- writer.WritePropertyName("content-type");
- writer.WriteStringValue("text/plain");
+ writer.WritePropertyName("content-type");
+ writer.WriteStringValue("text/plain");
+ }
if (sensitive)
{
@@ -232,6 +251,15 @@ public class Pleroma
writer.WriteStringValue(language);
}
+ if (attachments != null)
+ {
+ writer.WritePropertyName("media_ids");
+ writer.WriteStartArray();
+ foreach (MediaID media in attachments)
+ writer.WriteStringValue(media.ID);
+ writer.WriteEndArray();
+ }
+
if (visibility != StatusVisibility.Public)
{
writer.WritePropertyName("visibility");
@@ -250,6 +278,71 @@ public class Pleroma
return Retry(req)!;
}
+ static string CommonMIMEString(CommonMIMEs mime) => mime switch
+ {
+ CommonMIMEs.PNG => "image/png",
+ CommonMIMEs.JPEG => "image/jpeg",
+ CommonMIMEs.GIF => "image/gif",
+ CommonMIMEs.MP4 => "video/mp4",
+ CommonMIMEs.WEBM => "video/webm",
+ CommonMIMEs.MOV => "video/quicktime",
+ CommonMIMEs.WAV => "audio/vnd.wav",
+ CommonMIMEs.MP3 => "audio/mpeg",
+ CommonMIMEs.OGG => "audio/ogg",
+ CommonMIMEs.Text => "text/plain",
+ _ => throw new ArgumentException("Unknown common MIME", nameof(mime)),
+ };
+
+ ///
+ /// Uploads a file to the server. The resulting attachment can then be included in a status as an attachment.
+ ///
+ /// The MIME type of the file.
+ /// An array containing the file's contents.
+ /// A handle of the uploaded file, including its ID and URLs.
+ public Task Upload(CommonMIMEs mime, byte[] file) => Upload(CommonMIMEString(mime), file);
+
+ ///
+ /// Uploads a file to the server. The resulting attachment can then be included in a status as an attachment.
+ ///
+ /// The MIME type of the file.
+ /// An array containing the file's contents.
+ /// A handle of the uploaded file, including its ID and URLs.
+ public Task Upload(string mime, byte[] file) => Upload(mime, new ByteArrayContent(file));
+
+ ///
+ /// Uploads a file to the server. The resulting attachment can then be included in a status as an attachment.
+ ///
+ /// The MIME type of the file.
+ /// A stream of the file's contents.
+ /// A handle of the uploaded file, including its ID and URLs.
+ public Task Upload(CommonMIMEs mime, Stream stream) => Upload(CommonMIMEString(mime), stream);
+
+ ///
+ /// Uploads a file to the server. The resulting attachment can then be included in a status as an attachment.
+ ///
+ /// The MIME type of the file.
+ /// A stream of the file's contents.
+ /// A handle of the uploaded file, including its ID and URLs.
+ public Task Upload(string mime, Stream stream) => Upload(mime, new StreamContent(stream));
+
+ async Task Upload(string mime, HttpContent content)
+ {
+ content.Headers.ContentType = new MediaTypeHeaderValue(mime);
+ content.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
+ {
+ Name = "\"file\"",
+ FileName = $"\"{Random.Shared.NextInt64()}\"",
+ };
+
+ MultipartFormDataContent form = new MultipartFormDataContent();
+ form.Add(content);
+
+ HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, "/api/v1/media");
+ req.Content = form;
+
+ return (await Retry(req))!;
+ }
+
///
/// Reposts/boosts/shares a status.
///