diff --git a/Pleroma/Models/Notification.cs b/Pleroma/Models/Notification.cs new file mode 100644 index 0000000..4f6b795 --- /dev/null +++ b/Pleroma/Models/Notification.cs @@ -0,0 +1,212 @@ +namespace Uwaa.Pleroma; + +public class Notification : ASObject +{ + /// + /// The account that performed the action that generated the notification. + /// + [JsonPropertyName("account")] + public Account Account { get; set; } = null!; + + [JsonPropertyName("created_at")] + public DateTime CreatedAt { get; set; } + + /// + /// Group key shared by similar notifications + /// + [JsonPropertyName("group_key")] + public string GroupKey { get; set; } = null!; + + /// + /// Status that was the object of the notification, e.g. in mentions, reblogs, favourites, or polls. + /// + [JsonPropertyName("status")] + public Status? Status { get; set; } + + /// + /// The type of event that resulted in the notification. + /// + [JsonPropertyName("type")] + public NotificationType Type { get; set; } + + [JsonConstructor()] + internal Notification() + { + } +} + +[JsonConverter(typeof(NotificationIDConverter))] +public readonly struct NotificationID(string id) : IEquatable +{ + public static implicit operator NotificationID(string id) => new NotificationID(id); + + public static implicit operator NotificationID(Notification notification) => new NotificationID(notification.ID); + + public readonly string ID = id; + + public override string ToString() => ID; + + + public static bool operator ==(NotificationID left, NotificationID right) => left.Equals(right); + + public static bool operator !=(NotificationID left, NotificationID right) => !(left == right); + + public override bool Equals(object? obj) => (obj is NotificationID id && Equals(id)) || (obj is Notification notification && Equals(notification)); + + public bool Equals(NotificationID other) => ID == other.ID; + + public override int GetHashCode() => ID.GetHashCode(); +} + +class NotificationIDConverter : JsonConverter +{ + public override bool CanConvert(Type type) => type.IsAssignableTo(typeof(NotificationID)); + + public override NotificationID Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new NotificationID(reader.GetString() ?? throw new NullReferenceException("Expected a string, got null")); + } + + public override void Write(Utf8JsonWriter writer, NotificationID value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ID); + } +} + +[JsonConverter(typeof(NotificationTypeConverter))] +public enum NotificationType +{ + Unknown, + + /// + /// Someone followed you + /// + Follow, + + /// + /// Someone mentioned you in their status + /// + Mention, + + /// + /// Someone boosted one of your statuses + /// + Reblog, + + /// + /// Someone favourited one of your statuses + /// + Favourite, + + /// + /// A poll you have voted in or created has ended + /// + Poll, + + /// + /// Someone moved their account + /// + Move, + + /// + /// Someone reacted with emoji to your status + /// + EmojiReaction, + + /// + /// Someone mentioned you in a chat message + /// + ChatMention, + + /// + /// Someone was reported + /// + Report, + + /// + /// Someone you are subscribed to created a status + /// + Status, + + /// + /// A status you boosted has been edited + /// + Update, + + /// + /// Someone signed up (optionally sent to admins) + /// + Admin_SignUp, + + /// + /// A new report has been filed + /// + Admin_Report, +} + +/// +/// Helper functions for . +/// +public static class NotificationTypes +{ + public static NotificationType Parse(string typeStr) + { + return typeStr switch + { + "follow" => NotificationType.Follow, + "mention" => NotificationType.Mention, + "reblog" => NotificationType.Reblog, + "favourite" => NotificationType.Favourite, + "poll" => NotificationType.Poll, + "move" => NotificationType.Move, + "pleroma:emoji_reaction" => NotificationType.EmojiReaction, + "pleroma:chat_mention" => NotificationType.ChatMention, + "pleroma:report" => NotificationType.Report, + "status" => NotificationType.Status, + "update" => NotificationType.Update, + "admin.sign_up" => NotificationType.Admin_SignUp, + "admin.report" => NotificationType.Admin_Report, + _ => NotificationType.Unknown, + }; + } + + public static string ToString(this NotificationType type) + { + return type switch + { + NotificationType.Unknown => throw new NotImplementedException("Invalid notification type"), + NotificationType.Follow => "follow", + NotificationType.Mention => "mention", + NotificationType.Reblog => "reblog", + NotificationType.Favourite => "favourite", + NotificationType.Poll => "poll", + NotificationType.Move => "move", + NotificationType.EmojiReaction => "pleroma:emoji_reaction", + NotificationType.ChatMention => "pleroma:chat_mention", + NotificationType.Report => "pleroma:report", + NotificationType.Status => "status", + NotificationType.Update => "update", + NotificationType.Admin_SignUp => "admin.sign_up", + NotificationType.Admin_Report => "admin.report", + _ => throw new NotImplementedException("Unknown notification type"), + }; + } +} + +/// +/// Converts to and from enum values in lowercase. +/// +class NotificationTypeConverter : JsonConverter +{ + public override bool CanConvert(Type typeToConvert) => typeToConvert.IsEnum; + + public override NotificationType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return NotificationTypes.Parse(reader.GetString() ?? throw new NullReferenceException("Expected a string, got null")); + } + + public override void Write(Utf8JsonWriter writer, NotificationType value, JsonSerializerOptions options) + { + writer.WriteStringValue(NotificationTypes.ToString(value)); + } +} \ No newline at end of file diff --git a/Pleroma/Pleroma.cs b/Pleroma/Pleroma.cs index 4bdb275..5284846 100644 --- a/Pleroma/Pleroma.cs +++ b/Pleroma/Pleroma.cs @@ -51,6 +51,11 @@ public class Pleroma HttpClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(authorization); } + Task Retry(Func reqFactory) + { + return Retry(reqFactory); + } + async Task Retry(Func reqFactory) { while (true) @@ -61,7 +66,12 @@ public class Pleroma return default; if (res.Content == null) - throw new HttpRequestException("Server responded with no content"); + { + if (typeof(T) == typeof(object)) + return default; + else + throw new HttpRequestException("Server responded with no content"); + } string text = await res.Content.ReadAsStringAsync(); @@ -604,4 +614,79 @@ public class Pleroma if (limit != 20) addPair("limit", limit.ToString()); })))!; } + + /// + /// Notifications concerning the user. This API returns Link headers containing links to the next/previous page. However, the links can also be constructed dynamically using query params and id values. + /// + /// Return items older than this ID + /// Return the oldest items newer than this ID + /// Return the newest items newer than this ID + /// Return items past this number of items + /// Maximum number of items to return. Will be ignored if it's more than 40 + public Task GetNotifications(NotificationType[]? exclude_types = null, + string? account_id = null, + StatusVisibility[]? exclude_visibilities = null, + NotificationType[]? types = null, + bool with_muted = false, + string? max_id = null, + string? min_id = null, + string? since_id = null, + int offset = 0, + int limit = 20) + { + return Retry(() => new HttpRequestMessage(HttpMethod.Get, "/api/v1/notifications" + CreateQuery(addPair => + { + if (exclude_types != null) + foreach (NotificationType type in exclude_types) + addPair("exclude_types[]", NotificationTypes.ToString(type)); + + if (account_id != null) addPair("account_id", account_id); + + if (exclude_visibilities != null) + foreach (StatusVisibility visibility in exclude_visibilities) + addPair("exclude_visibilities[]", visibility.ToString().ToLowerInvariant()); + + if (types != null) + foreach (NotificationType type in types) + addPair("types[]", NotificationTypes.ToString(type)); + + if (with_muted) addPair("with_muted", "true"); + 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()); + })))!; + } + + /// + /// View information about a notification with a given ID. + /// + /// Notification ID + public Task GetNotification(NotificationID id) + { + return Retry(() => new HttpRequestMessage(HttpMethod.Get, $"/api/v1/notifications/{id}")); + } + + /// + /// Clear a single notification from the server. + /// + /// Notification ID + public Task Dismiss(NotificationID notification) + { + return Retry(() => new HttpRequestMessage(HttpMethod.Post, $"/api/v1/notifications/{notification}/dismiss")); + } + + /// + /// Clears multiple notifications from the server. + /// + /// Array of notification IDs to dismiss + public Task Dismiss(NotificationID[] notifications) + { + return Retry(() => new HttpRequestMessage(HttpMethod.Delete, $"/api/v1/notifications/destroy_multiple" + CreateQuery(addPair => + { + foreach (NotificationID id in notifications) + addPair("ids[]", id.ID); + }))); + } }