[backend/federation] Generate mention & reply notifications (ISH-45)
This commit is contained in:
parent
82b7b93681
commit
c46573ae37
3 changed files with 71 additions and 10 deletions
|
@ -38,6 +38,7 @@ public static class ServiceExtensions {
|
||||||
.AddScoped<WebFingerService>()
|
.AddScoped<WebFingerService>()
|
||||||
.AddScoped<SystemUserService>()
|
.AddScoped<SystemUserService>()
|
||||||
.AddScoped<DriveService>()
|
.AddScoped<DriveService>()
|
||||||
|
.AddScoped<NotificationService>()
|
||||||
.AddScoped<DatabaseMaintenanceService>()
|
.AddScoped<DatabaseMaintenanceService>()
|
||||||
.AddScoped<AuthorizedFetchMiddleware>()
|
.AddScoped<AuthorizedFetchMiddleware>()
|
||||||
.AddScoped<AuthenticationMiddleware>()
|
.AddScoped<AuthenticationMiddleware>()
|
||||||
|
|
|
@ -15,8 +15,9 @@ using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Iceshrimp.Backend.Core.Services;
|
namespace Iceshrimp.Backend.Core.Services;
|
||||||
|
|
||||||
using MentionQuad =
|
using MentionQuintuple =
|
||||||
(List<string> mentionedUserIds,
|
(List<string> mentionedUserIds,
|
||||||
|
List<string> mentionedLocalUserIds,
|
||||||
List<Note.MentionedUser> mentions,
|
List<Note.MentionedUser> mentions,
|
||||||
List<Note.MentionedUser> remoteMentions,
|
List<Note.MentionedUser> remoteMentions,
|
||||||
Dictionary<(string usernameLower, string webDomain), string> splitDomainMapping);
|
Dictionary<(string usernameLower, string webDomain), string> splitDomainMapping);
|
||||||
|
@ -34,7 +35,8 @@ public class NoteService(
|
||||||
UserRenderer userRenderer,
|
UserRenderer userRenderer,
|
||||||
MentionsResolver mentionsResolver,
|
MentionsResolver mentionsResolver,
|
||||||
MfmConverter mfmConverter,
|
MfmConverter mfmConverter,
|
||||||
DriveService driveSvc
|
DriveService driveSvc,
|
||||||
|
NotificationService notificationSvc
|
||||||
) {
|
) {
|
||||||
private readonly List<string> _resolverHistory = [];
|
private readonly List<string> _resolverHistory = [];
|
||||||
private int _recursionLimit = 100;
|
private int _recursionLimit = 100;
|
||||||
|
@ -49,7 +51,8 @@ public class NoteService(
|
||||||
if (text is { Length: > 100000 })
|
if (text is { Length: > 100000 })
|
||||||
throw GracefulException.BadRequest("Text cannot be longer than 100.000 characters");
|
throw GracefulException.BadRequest("Text cannot be longer than 100.000 characters");
|
||||||
|
|
||||||
var (mentionedUserIds, mentions, remoteMentions, splitDomainMapping) = await ResolveNoteMentionsAsync(text);
|
var (mentionedUserIds, mentionedLocalUserIds, mentions, remoteMentions, splitDomainMapping) =
|
||||||
|
await ResolveNoteMentionsAsync(text);
|
||||||
|
|
||||||
if (text != null)
|
if (text != null)
|
||||||
text = mentionsResolver.ResolveMentions(text, null, mentions, splitDomainMapping);
|
text = mentionsResolver.ResolveMentions(text, null, mentions, splitDomainMapping);
|
||||||
|
@ -80,6 +83,7 @@ public class NoteService(
|
||||||
user.NotesCount++;
|
user.NotesCount++;
|
||||||
await db.AddAsync(note);
|
await db.AddAsync(note);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
await notificationSvc.GenerateMentionNotifications(note, mentionedLocalUserIds);
|
||||||
|
|
||||||
var obj = await noteRenderer.RenderAsync(note, mentions);
|
var obj = await noteRenderer.RenderAsync(note, mentions);
|
||||||
var activity = ActivityRenderer.RenderCreate(obj, actor);
|
var activity = ActivityRenderer.RenderCreate(obj, actor);
|
||||||
|
@ -159,7 +163,8 @@ public class NoteService(
|
||||||
|
|
||||||
//TODO: resolve anything related to the note as well (attachments, emoji, etc)
|
//TODO: resolve anything related to the note as well (attachments, emoji, etc)
|
||||||
|
|
||||||
var (mentionedUserIds, mentions, remoteMentions, splitDomainMapping) = await ResolveNoteMentionsAsync(note);
|
var (mentionedUserIds, mentionedLocalUserIds, mentions, remoteMentions, splitDomainMapping) =
|
||||||
|
await ResolveNoteMentionsAsync(note);
|
||||||
|
|
||||||
var dbNote = new Note {
|
var dbNote = new Note {
|
||||||
Id = IdHelpers.GenerateSlowflakeId(),
|
Id = IdHelpers.GenerateSlowflakeId(),
|
||||||
|
@ -205,11 +210,13 @@ public class NoteService(
|
||||||
user.NotesCount++;
|
user.NotesCount++;
|
||||||
await db.Notes.AddAsync(dbNote);
|
await db.Notes.AddAsync(dbNote);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
await notificationSvc.GenerateMentionNotifications(dbNote, mentionedLocalUserIds);
|
||||||
|
await notificationSvc.GenerateReplyNotifications(dbNote, mentionedLocalUserIds);
|
||||||
logger.LogDebug("Note {id} created successfully", dbNote.Id);
|
logger.LogDebug("Note {id} created successfully", dbNote.Id);
|
||||||
return dbNote;
|
return dbNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<MentionQuad> ResolveNoteMentionsAsync(ASNote note) {
|
private async Task<MentionQuintuple> ResolveNoteMentionsAsync(ASNote note) {
|
||||||
var mentionTags = note.Tags?.OfType<ASMention>().Where(p => p.Href != null) ?? [];
|
var mentionTags = note.Tags?.OfType<ASMention>().Where(p => p.Href != null) ?? [];
|
||||||
var users = await mentionTags
|
var users = await mentionTags
|
||||||
.Select(async p => {
|
.Select(async p => {
|
||||||
|
@ -225,7 +232,7 @@ public class NoteService(
|
||||||
return ResolveNoteMentions(users.Where(p => p != null).Select(p => p!).ToList());
|
return ResolveNoteMentions(users.Where(p => p != null).Select(p => p!).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<MentionQuad> ResolveNoteMentionsAsync(string? text) {
|
private async Task<MentionQuintuple> ResolveNoteMentionsAsync(string? text) {
|
||||||
var users = text != null
|
var users = text != null
|
||||||
? await MfmParser.Parse(text)
|
? await MfmParser.Parse(text)
|
||||||
.SelectMany(p => p.Children.Append(p))
|
.SelectMany(p => p.Children.Append(p))
|
||||||
|
@ -245,8 +252,9 @@ public class NoteService(
|
||||||
return ResolveNoteMentions(users.Where(p => p != null).Select(p => p!).ToList());
|
return ResolveNoteMentions(users.Where(p => p != null).Select(p => p!).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private MentionQuad ResolveNoteMentions(IReadOnlyCollection<User> users) {
|
private MentionQuintuple ResolveNoteMentions(IReadOnlyCollection<User> users) {
|
||||||
var userIds = users.Select(p => p.Id).Distinct().ToList();
|
var userIds = users.Select(p => p.Id).Distinct().ToList();
|
||||||
|
var localUserIds = users.Where(p => p.Host == null).Select(p => p.Id).Distinct().ToList();
|
||||||
|
|
||||||
var remoteUsers = users.Where(p => p is { Host: not null, Uri: not null })
|
var remoteUsers = users.Where(p => p is { Host: not null, Uri: not null })
|
||||||
.ToList();
|
.ToList();
|
||||||
|
@ -274,7 +282,7 @@ public class NoteService(
|
||||||
|
|
||||||
var mentions = remoteMentions.Concat(localMentions).ToList();
|
var mentions = remoteMentions.Concat(localMentions).ToList();
|
||||||
|
|
||||||
return (userIds, mentions, remoteMentions, splitDomainMapping);
|
return (userIds, localUserIds, mentions, remoteMentions, splitDomainMapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<DriveFile>> ProcessAttachmentsAsync(
|
private async Task<List<DriveFile>> ProcessAttachmentsAsync(
|
||||||
|
@ -283,7 +291,8 @@ public class NoteService(
|
||||||
if (attachments is not { Count: > 0 }) return [];
|
if (attachments is not { Count: > 0 }) return [];
|
||||||
var result = await attachments
|
var result = await attachments
|
||||||
.OfType<ASDocument>()
|
.OfType<ASDocument>()
|
||||||
.Select(p => driveSvc.StoreFile(p.Url?.Id, user, p.Sensitive ?? sensitive, p.Description, p.MediaType))
|
.Select(p => driveSvc.StoreFile(p.Url?.Id, user, p.Sensitive ?? sensitive, p.Description,
|
||||||
|
p.MediaType))
|
||||||
.AwaitAllNoConcurrencyAsync();
|
.AwaitAllNoConcurrencyAsync();
|
||||||
|
|
||||||
return result.Where(p => p != null).Cast<DriveFile>().ToList();
|
return result.Where(p => p != null).Cast<DriveFile>().ToList();
|
||||||
|
|
51
Iceshrimp.Backend/Core/Services/NotificationService.cs
Normal file
51
Iceshrimp.Backend/Core/Services/NotificationService.cs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using Iceshrimp.Backend.Core.Database;
|
||||||
|
using Iceshrimp.Backend.Core.Database.Tables;
|
||||||
|
using Iceshrimp.Backend.Core.Helpers;
|
||||||
|
|
||||||
|
namespace Iceshrimp.Backend.Core.Services;
|
||||||
|
|
||||||
|
public class NotificationService(
|
||||||
|
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameterInConstructor")]
|
||||||
|
DatabaseContext db
|
||||||
|
) {
|
||||||
|
public async Task GenerateMentionNotifications(Note note, IReadOnlyCollection<string> mentionedLocalUserIds) {
|
||||||
|
if (mentionedLocalUserIds.Count == 0) return;
|
||||||
|
|
||||||
|
var notifications = mentionedLocalUserIds
|
||||||
|
.Where(p => p != note.UserId)
|
||||||
|
.Select(p => new Notification {
|
||||||
|
Id = IdHelpers.GenerateSlowflakeId(),
|
||||||
|
CreatedAt = DateTime.UtcNow,
|
||||||
|
Note = note,
|
||||||
|
NotifierId = note.UserId,
|
||||||
|
NotifieeId = p,
|
||||||
|
Type = Notification.NotificationType.Mention
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.AddRangeAsync(notifications);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task GenerateReplyNotifications(Note note, IReadOnlyCollection<string> mentionedLocalUserIds) {
|
||||||
|
if (note.Visibility != Note.NoteVisibility.Specified) return;
|
||||||
|
if (note.VisibleUserIds.Count == 0) return;
|
||||||
|
|
||||||
|
var users = mentionedLocalUserIds.Concat(note.VisibleUserIds).Distinct().Except(mentionedLocalUserIds).ToList();
|
||||||
|
if (users.Count == 0) return;
|
||||||
|
|
||||||
|
var notifications = users
|
||||||
|
.Where(p => p != note.UserId)
|
||||||
|
.Select(p => new Notification {
|
||||||
|
Id = IdHelpers.GenerateSlowflakeId(),
|
||||||
|
CreatedAt = DateTime.UtcNow,
|
||||||
|
Note = note,
|
||||||
|
NotifierId = note.UserId,
|
||||||
|
NotifieeId = p,
|
||||||
|
Type = Notification.NotificationType.Reply
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.AddRangeAsync(notifications);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue