using MongoDB.Driver; using MongoDB.Entities; using PrivaPub.Models.Social; namespace PrivaPub.Domain.Social { public static class Notifications { public static async Task Add(string avatarId, NotificationType type, string fromAccountId, string fromActorUri, string postId, CancellationToken token, string emoji = default, string emojiUrl = default, string text = default) { if (string.IsNullOrEmpty(avatarId) || avatarId == fromAccountId && type != NotificationType.Poll) return; if ((await Relationships.Hidden.AuthorsHiddenFrom(avatarId, new[] { fromActorUri }, forNotifications: true, token)).Count > 0) return; var action = await NotificationPolicies.Judge(avatarId, type, fromAccountId, fromActorUri, postId, token); if (action == NotificationAction.Drop) return; try { var notification = new Notification { AvatarId = avatarId, Type = type, FromAccountId = fromAccountId, FromActorURI = fromActorUri, PostId = postId, Emoji = emoji, EmojiURL = emojiUrl, Text = text, DedupeKey = emoji == default ? $"{type}|{avatarId}|{fromActorUri}|{postId}" : $"{type}|{avatarId}|{fromActorUri}|{postId}|{emoji}", Filtered = action == NotificationAction.Filter }; await DB.Default.SaveAsync(notification, token); // filtered: in its account's request, told to no stream if (notification.Filtered) { await NotificationPolicies.Requested(notification, token); return; } var told = new Timelines.Streams.Event("notification", notification.ID); Timelines.Streams.ToPersona(avatarId, new Timelines.Streams.Key(Timelines.Streams.User), told); Timelines.Streams.ToPersona(avatarId, new Timelines.Streams.Key(Timelines.Streams.UserNotification), told); } catch (MongoWriteException ex) when (ex.WriteError?.Category == ServerErrorCategory.DuplicateKey) { } } // A warning from this server's staff (completion program R6): Mastodon's moderation_warning, from the persona itself as // Mastodon's are (an AccountWarning has no account behind it), never judged by the notification policy, one for each // act. A suspended persona reads it once its suspension is lifted; until then its login sees it suspended in its list // of personas (/clientapi/avatar/private/list). public static async Task Warn(string avatarId, string personaUri, string action, string text, IReadOnlyList postIds, CancellationToken token) { if (string.IsNullOrEmpty(avatarId)) return; var ids = postIds?.Where(id => !string.IsNullOrEmpty(id)).Distinct().ToList() ?? new List(); var notification = new Notification { AvatarId = avatarId, Type = NotificationType.ModerationWarning, FromAccountId = avatarId, FromActorURI = personaUri, PostId = ids.FirstOrDefault(), StatusIds = ids.Count > 1 ? ids : null, Text = string.IsNullOrWhiteSpace(text) ? default : text.Trim(), WarningAction = action, DedupeKey = $"{NotificationType.ModerationWarning}|{avatarId}|staff|{MongoDB.Bson.ObjectId.GenerateNewId()}" }; await DB.Default.SaveAsync(notification, CancellationToken.None);//an act that was done is told even when its request went away var told = new Timelines.Streams.Event("notification", notification.ID); Timelines.Streams.ToPersona(avatarId, new Timelines.Streams.Key(Timelines.Streams.User), told); Timelines.Streams.ToPersona(avatarId, new Timelines.Streams.Key(Timelines.Streams.UserNotification), told); } } }