From 27b3be774eb8de7c97d3bb435a5e1c1bda4de2ee Mon Sep 17 00:00:00 2001 From: Laura Hausmann Date: Thu, 22 Feb 2024 00:22:43 +0100 Subject: [PATCH] [backend/federation] Handle quotes --- .../Mastodon/Renderers/NoteRenderer.cs | 13 +++++++++---- .../ActivityPub/ActivityHandlerService.cs | 6 ++---- .../Federation/ActivityStreams/Types/ASNote.cs | 12 ++++++++++++ Iceshrimp.Backend/Core/Services/NoteService.cs | 18 +++++++++++++++--- .../Core/Services/NotificationService.cs | 4 ++-- 5 files changed, 40 insertions(+), 13 deletions(-) diff --git a/Iceshrimp.Backend/Controllers/Mastodon/Renderers/NoteRenderer.cs b/Iceshrimp.Backend/Controllers/Mastodon/Renderers/NoteRenderer.cs index c98021fc..a6cad8be 100644 --- a/Iceshrimp.Backend/Controllers/Mastodon/Renderers/NoteRenderer.cs +++ b/Iceshrimp.Backend/Controllers/Mastodon/Renderers/NoteRenderer.cs @@ -23,10 +23,15 @@ public class NoteRenderer( ) { var uri = note.Uri ?? note.GetPublicUri(config.Value); - var renote = note.Renote != null && recurse > 0 + var renote = note is { Renote: not null, IsQuote: false } && recurse > 0 + ? await RenderAsync(note.Renote, user, accounts, mentions, attachments, likeCounts, likedNotes, 0) + : null; + var quote = note is { Renote: not null, IsQuote: true } && recurse > 0 ? await RenderAsync(note.Renote, user, accounts, mentions, attachments, likeCounts, likedNotes, --recurse) : null; - var text = note.Text; //TODO: append quote uri + var text = note.Text; + if (quote != null && text != null && !text.EndsWith(quote.Url) && !text.EndsWith(quote.Uri)) + text += $"\n\nRE: {quote.Url}"; //TODO: render as inline quote var likeCount = likeCounts?.GetValueOrDefault(note.Id, 0) ?? await db.NoteLikes.CountAsync(p => p.Note == note); var liked = likedNotes?.Contains(note.Id) ?? await db.NoteLikes.AnyAsync(p => p.Note == note && p.User == user); @@ -87,8 +92,8 @@ public class NoteRenderer( Account = account, ReplyId = note.ReplyId, ReplyUserId = note.ReplyUserId, - Renote = renote, //TODO: check if it's a pure renote - Quote = renote, //TODO: see above + Renote = renote, + Quote = quote, ContentType = "text/x.misskeymarkdown", CreatedAt = note.CreatedAt.ToStringMastodon(), EditedAt = note.UpdatedAt?.ToStringMastodon(), diff --git a/Iceshrimp.Backend/Core/Federation/ActivityPub/ActivityHandlerService.cs b/Iceshrimp.Backend/Core/Federation/ActivityPub/ActivityHandlerService.cs index 43f01515..4f168816 100644 --- a/Iceshrimp.Backend/Core/Federation/ActivityPub/ActivityHandlerService.cs +++ b/Iceshrimp.Backend/Core/Federation/ActivityPub/ActivityHandlerService.cs @@ -236,9 +236,7 @@ public class ActivityHandlerService( throw GracefulException.UnprocessableEntity("Invalid or unsupported announce object"); var dbNote = await noteSvc.ResolveNoteAsync(note.Id, note.VerifiedFetch ? note : null); - var renote = await noteSvc.CreateNoteAsync(resolvedActor, announce.GetVisibility(activity.Actor), - renote: dbNote); - await notificationSvc.GenerateRenoteNotification(renote); + await noteSvc.CreateNoteAsync(resolvedActor, announce.GetVisibility(activity.Actor), renote: dbNote); return; } default: @@ -421,4 +419,4 @@ public class ActivityHandlerService( p.Notifier == actor) .ExecuteDeleteAsync(); } -} +} \ No newline at end of file diff --git a/Iceshrimp.Backend/Core/Federation/ActivityStreams/Types/ASNote.cs b/Iceshrimp.Backend/Core/Federation/ActivityStreams/Types/ASNote.cs index e0a4cb48..3ba99a38 100644 --- a/Iceshrimp.Backend/Core/Federation/ActivityStreams/Types/ASNote.cs +++ b/Iceshrimp.Backend/Core/Federation/ActivityStreams/Types/ASNote.cs @@ -14,6 +14,18 @@ public class ASNote : ASObject [JC(typeof(VC))] public string? MkContent { get; set; } + [J("https://misskey-hub.net/ns#_misskey_quote")] + [JC(typeof(VC))] + public string? MkQuote { get; set; } + + [J($"{Constants.ActivityStreamsNs}#quoteUrl")] + [JC(typeof(VC))] + public string? QuoteUrl { get; set; } + + [J("http://fedibird.com/ns#quoteUri")] + [JC(typeof(VC))] + public string? QuoteUri { get; set; } + [J($"{Constants.ActivityStreamsNs}#content")] [JC(typeof(VC))] public string? Content { get; set; } diff --git a/Iceshrimp.Backend/Core/Services/NoteService.cs b/Iceshrimp.Backend/Core/Services/NoteService.cs index e2de7abd..467db437 100644 --- a/Iceshrimp.Backend/Core/Services/NoteService.cs +++ b/Iceshrimp.Backend/Core/Services/NoteService.cs @@ -99,6 +99,8 @@ public class NoteService( await db.SaveChangesAsync(); eventSvc.RaiseNotePublished(this, note); await notificationSvc.GenerateMentionNotifications(note, mentionedLocalUserIds); + await notificationSvc.GenerateReplyNotifications(note, mentionedLocalUserIds); + await notificationSvc.GenerateRenoteNotification(note); if (user.Host != null) return note; @@ -300,7 +302,7 @@ public class NoteService( if (actor.IsSuspended) throw GracefulException.Forbidden("User is suspended"); - //TODO: resolve anything related to the note as well (attachments, emoji, etc) + //TODO: resolve emoji var (mentionedUserIds, mentionedLocalUserIds, mentions, remoteMentions, splitDomainMapping) = await ResolveNoteMentionsAsync(note); @@ -308,6 +310,8 @@ public class NoteService( var createdAt = note.PublishedAt?.ToUniversalTime() ?? throw GracefulException.UnprocessableEntity("Missing or invalid PublishedAt field"); + var quoteUrl = note.MkQuote ?? note.QuoteUri ?? note.QuoteUrl; + var dbNote = new Note { Id = IdHelpers.GenerateSlowflakeId(createdAt), @@ -319,7 +323,8 @@ public class NoteService( CreatedAt = createdAt, UserHost = actor.Host, Visibility = note.GetVisibility(actor), - Reply = note.InReplyTo?.Id != null ? await ResolveNoteAsync(note.InReplyTo.Id) : null + Reply = note.InReplyTo?.Id != null ? await ResolveNoteAsync(note.InReplyTo.Id) : null, + Renote = quoteUrl != null ? await ResolveNoteAsync(quoteUrl) : null }; if (dbNote.Reply != null) @@ -327,6 +332,12 @@ public class NoteService( dbNote.ReplyUserId = dbNote.Reply.UserId; dbNote.ReplyUserHost = dbNote.Reply.UserHost; } + + if (dbNote.Renote != null) + { + dbNote.RenoteUserId = dbNote.Renote.UserId; + dbNote.RenoteUserHost = dbNote.Renote.UserHost; + } if (dbNote.Text is { Length: > 100000 }) throw GracefulException.UnprocessableEntity("Content cannot be longer than 100.000 characters"); @@ -369,6 +380,7 @@ public class NoteService( eventSvc.RaiseNotePublished(this, dbNote); await notificationSvc.GenerateMentionNotifications(dbNote, mentionedLocalUserIds); await notificationSvc.GenerateReplyNotifications(dbNote, mentionedLocalUserIds); + await notificationSvc.GenerateRenoteNotification(dbNote); logger.LogDebug("Note {id} created successfully", dbNote.Id); return dbNote; } @@ -655,4 +667,4 @@ public class NoteService( var dbNote = await ResolveNoteAsync(note) ?? throw new Exception("Cannot unregister like for unknown note"); await UnlikeNoteAsync(dbNote, actor); } -} +} \ No newline at end of file diff --git a/Iceshrimp.Backend/Core/Services/NotificationService.cs b/Iceshrimp.Backend/Core/Services/NotificationService.cs index 65dd5f2c..e1d5815b 100644 --- a/Iceshrimp.Backend/Core/Services/NotificationService.cs +++ b/Iceshrimp.Backend/Core/Services/NotificationService.cs @@ -185,7 +185,7 @@ public class NotificationService( { if (note.Renote is not { UserHost: null }) return; if (!note.VisibilityIsPublicOrHome && - await db.Notes.AnyAsync(p => p.Id == note.Id && p.IsVisibleFor(note.Renote.User))) + !await db.Notes.AnyAsync(p => p.Id == note.Id && p.IsVisibleFor(note.Renote.User))) return; var notification = new Notification @@ -195,7 +195,7 @@ public class NotificationService( Note = note, Notifiee = note.Renote.User, Notifier = note.User, - Type = Notification.NotificationType.Renote + Type = note.IsQuote ? Notification.NotificationType.Quote : Notification.NotificationType.Renote }; await db.AddAsync(notification);