diff --git a/Pleroma/ASObject.cs b/Pleroma/ASObject.cs index a10e33a..af8140d 100644 --- a/Pleroma/ASObject.cs +++ b/Pleroma/ASObject.cs @@ -1,10 +1,36 @@ -namespace Uwaa.Pleroma; +using System.Buffers.Binary; +using System.Collections; + +namespace Uwaa.Pleroma; /// /// Base class for ActivityStreams objects. /// public class ASObject { + public static UInt128 FromBase62(string id) + { + //TODO: Optimize + const string table = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + BitArray bits = new BitArray(id.Length * 6); + for (int i = 0; i < id.Length; i++) + { + int index = table.IndexOf(id[^(i + 1)]); + bits[i * 6] = (index & 1) != 0; + bits[i * 6 + 1] = (index & 2) != 0; + bits[i * 6 + 2] = (index & 4) != 0; + bits[i * 6 + 3] = (index & 8) != 0; + bits[i * 6 + 4] = (index & 16) != 0; + bits[i * 6 + 5] = (index & 32) != 0; + } + + byte[] bytes = new byte[16]; + bits.CopyTo(bytes, 0); + + return BinaryPrimitives.ReadUInt128LittleEndian(bytes); + } + /// /// Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mastodon's ids they are lexically sortable strings /// diff --git a/Pleroma/Models/Account.cs b/Pleroma/Models/Account.cs index 33d9a32..dd52bf0 100644 --- a/Pleroma/Models/Account.cs +++ b/Pleroma/Models/Account.cs @@ -17,9 +17,6 @@ public class Account : ASObject [JsonPropertyName("following_count")] public uint Following { get; set; } - [JsonPropertyName("local")] - public bool Local { get; set; } - [JsonPropertyName("locked")] public bool Locked { get; set; } diff --git a/Pleroma/Pleroma.cs b/Pleroma/Pleroma.cs index e77736a..8ee56c4 100644 --- a/Pleroma/Pleroma.cs +++ b/Pleroma/Pleroma.cs @@ -171,28 +171,28 @@ public class Pleroma int offset = 0, int limit = 20) { - return Retry(() => new HttpRequestMessage(HttpMethod.Get, $"/api/v1/accounts/{account.ID}/following" + CreateQuery(addPair => + string path = $"/api/v1/accounts/{account.ID}/following" + CreateQuery(addPair => { if (max_id != null) addPair("max_id", max_id); if (min_id != null) addPair("min_id", min_id); if (since_id != null) addPair("since_id", since_id); if (offset > 0) addPair("offset", offset.ToString()); if (limit != 20) addPair("limit", limit.ToString()); - })))!; + }); + return Retry(() => new HttpRequestMessage(HttpMethod.Get, path))!; } /// /// Sets a private note for the given account. /// - /// Account ID + /// Account ID /// Account note body - /// + /// The new relationship with the given account. public Task SetUserNote(AccountID account, string comment) { MemoryStream mem = new MemoryStream(); - + using (Utf8JsonWriter writer = new Utf8JsonWriter(mem, new JsonWriterOptions() { SkipValidation = true })) { - Utf8JsonWriter writer = new Utf8JsonWriter(mem, new JsonWriterOptions() { SkipValidation = true }); writer.WriteStartObject(); writer.WritePropertyName("comment"); @@ -215,7 +215,7 @@ public class Pleroma /// /// Gets the account's relationship with the given account. /// - /// Account ID + /// Account ID /// A relationship object for the requested account, if the account exists. public async Task GetRelationship(AccountID account) { @@ -246,7 +246,7 @@ public class Pleroma bool sensitive = false, int? expiresIn = null, StatusID? replyTo = null, - string? quoting = null, + StatusID? quoting = null, string? language = null, MediaID[]? attachments = null, string[]? to = null, @@ -256,9 +256,8 @@ public class Pleroma throw new ArgumentException("Cannot post nothing. Content and/or attachments must be provided."); MemoryStream mem = new MemoryStream(); - + using (Utf8JsonWriter writer = new Utf8JsonWriter(mem, new JsonWriterOptions() { SkipValidation = true })) { - Utf8JsonWriter writer = new Utf8JsonWriter(mem, new JsonWriterOptions() { SkipValidation = true }); writer.WriteStartObject(); if (content != null) @@ -288,10 +287,10 @@ public class Pleroma writer.WriteStringValue(replyTo.Value.ID); } - if (quoting != null) + if (quoting.HasValue) { writer.WritePropertyName("quote_id"); - writer.WriteStringValue(quoting); + writer.WriteStringValue(quoting.Value.ID); } if (language != null) @@ -434,8 +433,8 @@ public class Pleroma { MemoryStream mem = new MemoryStream(); + using (Utf8JsonWriter writer = new Utf8JsonWriter(mem, new JsonWriterOptions() { SkipValidation = true })) { - Utf8JsonWriter writer = new Utf8JsonWriter(mem, new JsonWriterOptions() { SkipValidation = true }); writer.WriteStartObject(); if (visibility.HasValue) @@ -532,7 +531,7 @@ public class Pleroma throw new ArgumentException("Invalid timeline", nameof(timeline)); } - return Retry(() => new HttpRequestMessage(HttpMethod.Get, $"/api/v1/timelines/{timelineName}" + CreateQuery(addPair => + string path = $"/api/v1/timelines/{timelineName}" + CreateQuery(addPair => { if (local) addPair("local", "true"); if (instance != null) addPair("instance", instance); @@ -545,7 +544,8 @@ public class Pleroma if (since_id != null) addPair("since_id", since_id); if (offset > 0) addPair("offset", offset.ToString()); if (limit != 20) addPair("limit", limit.ToString()); - })))!; + }); + return Retry(() => new HttpRequestMessage(HttpMethod.Get, path))!; } @@ -579,8 +579,7 @@ public class Pleroma int offset = 0, int limit = 20) { - - return Retry(() => new HttpRequestMessage(HttpMethod.Get, $"/api/v1/accounts/{WebUtility.UrlEncode(account.ID)}/statuses" + CreateQuery(addPair => + string path = $"/api/v1/accounts/{WebUtility.UrlEncode(account.ID)}/statuses" + CreateQuery(addPair => { if (pinned) addPair("pinned", "true"); if (tagged != null) addPair("tagged", tagged); @@ -596,7 +595,8 @@ public class Pleroma if (since_id != null) addPair("since_id", since_id); if (offset > 0) addPair("offset", offset.ToString()); if (limit != 20) addPair("limit", limit.ToString()); - }))); + }); + return Retry(() => new HttpRequestMessage(HttpMethod.Get, path)); } /// @@ -638,7 +638,7 @@ public class Pleroma int offset = 0, int limit = 20) { - return Retry(() => new HttpRequestMessage(HttpMethod.Get, "/api/v2/search" + CreateQuery(addPair => + string path = "/api/v2/search" + CreateQuery(addPair => { addPair("q", query); if (account_id != null) addPair("account_id", account_id); @@ -650,7 +650,8 @@ public class Pleroma if (since_id != null) addPair("since_id", since_id); if (offset > 0) addPair("offset", offset.ToString()); if (limit != 20) addPair("limit", limit.ToString()); - })))!; + }); + return Retry(() => new HttpRequestMessage(HttpMethod.Get, path))!; } /// @@ -692,7 +693,7 @@ public class Pleroma int offset = 0, int limit = 20) { - return Retry(() => new HttpRequestMessage(HttpMethod.Get, "/api/v1/notifications" + CreateQuery(addPair => + string path = "/api/v1/notifications" + CreateQuery(addPair => { if (exclude_types != null) foreach (NotificationType type in exclude_types) @@ -714,7 +715,8 @@ public class Pleroma if (since_id != null) addPair("since_id", since_id); if (offset > 0) addPair("offset", offset.ToString()); if (limit != 20) addPair("limit", limit.ToString()); - })))!; + }); + return Retry(() => new HttpRequestMessage(HttpMethod.Get, path))!; } /// @@ -723,7 +725,7 @@ public class Pleroma /// Notification ID public Task GetNotification(NotificationID id) { - return Retry(() => new HttpRequestMessage(HttpMethod.Get, $"/api/v1/notifications/{id}")); + return Retry(() => new HttpRequestMessage(HttpMethod.Get, $"/api/v1/notifications/{WebUtility.UrlEncode(id.ID)}")); } /// @@ -732,7 +734,7 @@ public class Pleroma /// Notification ID public Task Dismiss(NotificationID notification) { - return Retry(() => new HttpRequestMessage(HttpMethod.Post, $"/api/v1/notifications/{notification}/dismiss")); + return Retry(() => new HttpRequestMessage(HttpMethod.Post, $"/api/v1/notifications/{WebUtility.UrlEncode(notification.ID)}/dismiss")); } /// @@ -744,11 +746,12 @@ public class Pleroma if (notifications.Length == 0) return Task.CompletedTask; - return Retry(() => new HttpRequestMessage(HttpMethod.Delete, $"/api/v1/notifications/destroy_multiple" + CreateQuery(addPair => + string path = $"/api/v1/notifications/destroy_multiple" + CreateQuery(addPair => { foreach (NotificationID id in notifications) addPair("ids[]", id.ID); - }))); + }); + return Retry(() => new HttpRequestMessage(HttpMethod.Delete, path)); } /// @@ -760,11 +763,33 @@ public class Pleroma if (notifications.Length == 0) return Task.CompletedTask; - return Retry(() => new HttpRequestMessage(HttpMethod.Delete, $"/api/v1/notifications/destroy_multiple" + CreateQuery(addPair => + string path = $"/api/v1/notifications/destroy_multiple" + CreateQuery(addPair => { foreach (NotificationID id in notifications) addPair("ids[]", id.ID); - }))); + }); + return Retry(() => new HttpRequestMessage(HttpMethod.Delete, path)); + } + + /// + /// Adds a unicode reaction to a status. + /// + /// The status to react to. + /// The unicode emoji. + /// The new state of the status, if it exists. + public Task React(StatusID status, char emoji) => ReactInline(status, emoji.ToString()); + + /// + /// Adds a custom reaction to a status. + /// + /// The status to react to. + /// The custom emoji's name. + /// The new state of the status, if it exists. + public Task React(StatusID status, string emojiName) => ReactInline(status, $":{emojiName}:"); + + Task ReactInline(StatusID status, string emoji) + { + return Retry(() => new HttpRequestMessage(HttpMethod.Put, $"/api/v1/pleroma/statuses/{WebUtility.UrlEncode(status.ID)}/reactions/{WebUtility.UrlEncode(emoji)}")); } } @@ -799,7 +824,7 @@ public class PleromaAdmin : Pleroma ActorType[]? actor_types = null, string[]? tags = null) { - return Retry(() => new HttpRequestMessage(HttpMethod.Get, $"/api/v1/pleroma/admin/users" + CreateQuery(addPair => + string path = "/api/v1/pleroma/admin/users" + CreateQuery(addPair => { string? typeStr = type switch { @@ -835,15 +860,22 @@ public class PleromaAdmin : Pleroma if (tags != null) foreach (string tag in tags) addPair("tags[]", tag); - })))!; + }); + return Retry(() => new HttpRequestMessage(HttpMethod.Get, path))!; } - public Task ChangeScope(StatusID status, bool? sensitive = null, StatusVisibility? visibility = null) + /// + /// Modifies the sensitive and/or visibility of a status. + /// + /// The status ID to modify. + /// If non-null, the status will be made sensitive (if true) or not sensitive (if false). + /// If non-null, the status's visibility will be set to this. + /// Returns the new state of the status. Returns null if the status doesn't exist. + public Task ChangeScope(StatusID status, bool? sensitive = null, StatusVisibility? visibility = null) { MemoryStream mem = new MemoryStream(); - + using (Utf8JsonWriter writer = new Utf8JsonWriter(mem, new JsonWriterOptions() { SkipValidation = true })) { - Utf8JsonWriter writer = new Utf8JsonWriter(mem, new JsonWriterOptions() { SkipValidation = true }); writer.WriteStartObject(); if (sensitive.HasValue) @@ -865,19 +897,50 @@ public class PleromaAdmin : Pleroma return Retry(() => { mem.Position = 0; - HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Put, $"/api/v1/pleroma/admin/statuses{WebUtility.UrlEncode(status.ID)}"); + HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Put, $"/api/v1/pleroma/admin/statuses/{WebUtility.UrlEncode(status.ID)}"); req.Content = new StreamContent(mem); req.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); return req; - })!; + }); } public Task GetModerationLog(int page = 1) { - return Retry(() => new HttpRequestMessage(HttpMethod.Get, $"/api/v1/pleroma/admin/moderation_log" + CreateQuery(addPair => + string path = "/api/v1/pleroma/admin/moderation_log" + CreateQuery(addPair => { addPair("page", page.ToString()); - })))!; + }); + return Retry(() => new HttpRequestMessage(HttpMethod.Get, path))!; + } + + /// + /// Deactivates or deletes one or more users by nickname. + /// + /// If the user is pending approval, this will delete the user entirely. If the user is active, the user will be deactivated. Does nothing if the user is already deactivated. + /// The nicknames of the users to deactivate or delete. + /// An array of nicknames which were successfully deactivated or deleted. + public Task DeleteUsers(params string[] nicknames) + { + MemoryStream mem = new MemoryStream(); + using (Utf8JsonWriter writer = new Utf8JsonWriter(mem, new JsonWriterOptions() { SkipValidation = true })) + { + writer.WriteStartObject(); + writer.WriteStartArray("nicknames"); + foreach (string nickname in nicknames) + writer.WriteStringValue(nickname); + writer.WriteEndArray(); + writer.WriteEndObject(); + writer.Flush(); + } + + return Retry(() => + { + mem.Position = 0; + HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Delete, "/api/v1/pleroma/admin/users"); + req.Content = new StreamContent(mem); + req.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + return req; + })!; } }