[backend/federation] Handle quotes

This commit is contained in:
Laura Hausmann 2024-02-22 00:22:43 +01:00
parent a02af802f1
commit 27b3be774e
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
5 changed files with 40 additions and 13 deletions

View file

@ -23,10 +23,15 @@ public class NoteRenderer(
) )
{ {
var uri = note.Uri ?? note.GetPublicUri(config.Value); 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) ? await RenderAsync(note.Renote, user, accounts, mentions, attachments, likeCounts, likedNotes, --recurse)
: null; : 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 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); 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, Account = account,
ReplyId = note.ReplyId, ReplyId = note.ReplyId,
ReplyUserId = note.ReplyUserId, ReplyUserId = note.ReplyUserId,
Renote = renote, //TODO: check if it's a pure renote Renote = renote,
Quote = renote, //TODO: see above Quote = quote,
ContentType = "text/x.misskeymarkdown", ContentType = "text/x.misskeymarkdown",
CreatedAt = note.CreatedAt.ToStringMastodon(), CreatedAt = note.CreatedAt.ToStringMastodon(),
EditedAt = note.UpdatedAt?.ToStringMastodon(), EditedAt = note.UpdatedAt?.ToStringMastodon(),

View file

@ -236,9 +236,7 @@ public class ActivityHandlerService(
throw GracefulException.UnprocessableEntity("Invalid or unsupported announce object"); throw GracefulException.UnprocessableEntity("Invalid or unsupported announce object");
var dbNote = await noteSvc.ResolveNoteAsync(note.Id, note.VerifiedFetch ? note : null); var dbNote = await noteSvc.ResolveNoteAsync(note.Id, note.VerifiedFetch ? note : null);
var renote = await noteSvc.CreateNoteAsync(resolvedActor, announce.GetVisibility(activity.Actor), await noteSvc.CreateNoteAsync(resolvedActor, announce.GetVisibility(activity.Actor), renote: dbNote);
renote: dbNote);
await notificationSvc.GenerateRenoteNotification(renote);
return; return;
} }
default: default:

View file

@ -14,6 +14,18 @@ public class ASNote : ASObject
[JC(typeof(VC))] [JC(typeof(VC))]
public string? MkContent { get; set; } 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")] [J($"{Constants.ActivityStreamsNs}#content")]
[JC(typeof(VC))] [JC(typeof(VC))]
public string? Content { get; set; } public string? Content { get; set; }

View file

@ -99,6 +99,8 @@ public class NoteService(
await db.SaveChangesAsync(); await db.SaveChangesAsync();
eventSvc.RaiseNotePublished(this, note); eventSvc.RaiseNotePublished(this, note);
await notificationSvc.GenerateMentionNotifications(note, mentionedLocalUserIds); await notificationSvc.GenerateMentionNotifications(note, mentionedLocalUserIds);
await notificationSvc.GenerateReplyNotifications(note, mentionedLocalUserIds);
await notificationSvc.GenerateRenoteNotification(note);
if (user.Host != null) return note; if (user.Host != null) return note;
@ -300,7 +302,7 @@ public class NoteService(
if (actor.IsSuspended) if (actor.IsSuspended)
throw GracefulException.Forbidden("User is suspended"); 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) = var (mentionedUserIds, mentionedLocalUserIds, mentions, remoteMentions, splitDomainMapping) =
await ResolveNoteMentionsAsync(note); await ResolveNoteMentionsAsync(note);
@ -308,6 +310,8 @@ public class NoteService(
var createdAt = note.PublishedAt?.ToUniversalTime() ?? var createdAt = note.PublishedAt?.ToUniversalTime() ??
throw GracefulException.UnprocessableEntity("Missing or invalid PublishedAt field"); throw GracefulException.UnprocessableEntity("Missing or invalid PublishedAt field");
var quoteUrl = note.MkQuote ?? note.QuoteUri ?? note.QuoteUrl;
var dbNote = new Note var dbNote = new Note
{ {
Id = IdHelpers.GenerateSlowflakeId(createdAt), Id = IdHelpers.GenerateSlowflakeId(createdAt),
@ -319,7 +323,8 @@ public class NoteService(
CreatedAt = createdAt, CreatedAt = createdAt,
UserHost = actor.Host, UserHost = actor.Host,
Visibility = note.GetVisibility(actor), 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) if (dbNote.Reply != null)
@ -328,6 +333,12 @@ public class NoteService(
dbNote.ReplyUserHost = dbNote.Reply.UserHost; 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 }) if (dbNote.Text is { Length: > 100000 })
throw GracefulException.UnprocessableEntity("Content cannot be longer than 100.000 characters"); throw GracefulException.UnprocessableEntity("Content cannot be longer than 100.000 characters");
if (dbNote.Cw is { Length: > 100000 }) if (dbNote.Cw is { Length: > 100000 })
@ -369,6 +380,7 @@ public class NoteService(
eventSvc.RaiseNotePublished(this, dbNote); eventSvc.RaiseNotePublished(this, dbNote);
await notificationSvc.GenerateMentionNotifications(dbNote, mentionedLocalUserIds); await notificationSvc.GenerateMentionNotifications(dbNote, mentionedLocalUserIds);
await notificationSvc.GenerateReplyNotifications(dbNote, mentionedLocalUserIds); await notificationSvc.GenerateReplyNotifications(dbNote, mentionedLocalUserIds);
await notificationSvc.GenerateRenoteNotification(dbNote);
logger.LogDebug("Note {id} created successfully", dbNote.Id); logger.LogDebug("Note {id} created successfully", dbNote.Id);
return dbNote; return dbNote;
} }

View file

@ -185,7 +185,7 @@ public class NotificationService(
{ {
if (note.Renote is not { UserHost: null }) return; if (note.Renote is not { UserHost: null }) return;
if (!note.VisibilityIsPublicOrHome && 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; return;
var notification = new Notification var notification = new Notification
@ -195,7 +195,7 @@ public class NotificationService(
Note = note, Note = note,
Notifiee = note.Renote.User, Notifiee = note.Renote.User,
Notifier = note.User, Notifier = note.User,
Type = Notification.NotificationType.Renote Type = note.IsQuote ? Notification.NotificationType.Quote : Notification.NotificationType.Renote
}; };
await db.AddAsync(notification); await db.AddAsync(notification);