Files
SocialPub/PrivaPub/Domain/Social/Notifications.cs
T

99 lines
4.2 KiB
C#
Raw Normal View History

using MongoDB.Driver;
using MongoDB.Entities;
using PrivaPub.Models.Social;
namespace PrivaPub.Domain.Social
{
public static class Notifications
{
// told of every notification once it is kept for the persona's lists and streams: never one the notification policy
// filtered or dropped, nor one its blocks and mutes hid. Web Push hangs here (R9, WebPushDispatcher, set while the
// host runs; the command line sets nothing). Whatever it does never fails the notification.
public static Func<Notification, CancellationToken, Task> Told { get; set; }
static async Task Tell(Notification notification, CancellationToken token)
{
if (Told is not { } told)
return;
try
{
await told(notification, token);
}
catch (Exception)
{
//the hook logs its own failures
}
}
public static async Task Add(string avatarId, NotificationType type, string fromAccountId, string fromActorUri, string postId,
2026-10-07 21:10:58 +02:00
CancellationToken token, string emoji = default, string emojiUrl = default, string text = default)
{
if (string.IsNullOrEmpty(avatarId) || avatarId == fromAccountId && type != NotificationType.Poll)
return;
2026-10-01 12:13:57 +02:00
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
{
2026-10-05 04:04:08 +02:00
var notification = new Notification
{
AvatarId = avatarId,
Type = type,
FromAccountId = fromAccountId,
FromActorURI = fromActorUri,
PostId = postId,
2026-10-01 18:39:18 +02:00
Emoji = emoji,
EmojiURL = emojiUrl,
2026-10-07 21:10:58 +02:00
Text = text,
DedupeKey = emoji == default ? $"{type}|{avatarId}|{fromActorUri}|{postId}" : $"{type}|{avatarId}|{fromActorUri}|{postId}|{emoji}",
Filtered = action == NotificationAction.Filter
2026-10-05 04:04:08 +02:00
};
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;
}
2026-10-05 04:04:08 +02:00
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);
await Tell(notification, token);
}
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<string> postIds, CancellationToken token)
{
if (string.IsNullOrEmpty(avatarId))
return;
var ids = postIds?.Where(id => !string.IsNullOrEmpty(id)).Distinct().ToList() ?? new List<string>();
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);
await Tell(notification, CancellationToken.None);
}
}
}