[backend/api-shared] Clone NoteResponse / StatusEntity in streaming handlers' EnforceRenoteReplyVisibility functions (ISH-250)
This commit is contained in:
parent
bc50aa0259
commit
998a4412cb
5 changed files with 152 additions and 82 deletions
|
@ -7,7 +7,7 @@ using JI = System.Text.Json.Serialization.JsonIgnoreAttribute;
|
||||||
|
|
||||||
namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
|
namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
|
||||||
|
|
||||||
public class StatusEntity : IEntity
|
public class StatusEntity : IEntity, ICloneable
|
||||||
{
|
{
|
||||||
[J("content")] public required string? Content { get; set; }
|
[J("content")] public required string? Content { get; set; }
|
||||||
[J("uri")] public required string Uri { get; set; }
|
[J("uri")] public required string Uri { get; set; }
|
||||||
|
@ -79,6 +79,8 @@ public class StatusEntity : IEntity
|
||||||
_ => throw GracefulException.BadRequest($"Unknown visibility: {visibility}")
|
_ => throw GracefulException.BadRequest($"Unknown visibility: {visibility}")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public object Clone() => MemberwiseClone();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class StatusContext
|
public class StatusContext
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Iceshrimp.Backend.Controllers.Mastodon.Renderers;
|
using Iceshrimp.Backend.Controllers.Mastodon.Renderers;
|
||||||
|
using Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
|
||||||
using Iceshrimp.Backend.Core.Database.Tables;
|
using Iceshrimp.Backend.Core.Database.Tables;
|
||||||
|
|
||||||
namespace Iceshrimp.Backend.Controllers.Mastodon.Streaming.Channels;
|
namespace Iceshrimp.Backend.Controllers.Mastodon.Streaming.Channels;
|
||||||
|
@ -12,7 +13,7 @@ public class PublicChannel(
|
||||||
bool onlyMedia
|
bool onlyMedia
|
||||||
) : IChannel
|
) : IChannel
|
||||||
{
|
{
|
||||||
public readonly ILogger<PublicChannel> Logger =
|
private readonly ILogger<PublicChannel> _logger =
|
||||||
connection.Scope.ServiceProvider.GetRequiredService<ILogger<PublicChannel>>();
|
connection.Scope.ServiceProvider.GetRequiredService<ILogger<PublicChannel>>();
|
||||||
|
|
||||||
public string Name => name;
|
public string Name => name;
|
||||||
|
@ -45,23 +46,44 @@ public class PublicChannel(
|
||||||
connection.EventService.NoteDeleted -= OnNoteDeleted;
|
connection.EventService.NoteDeleted -= OnNoteDeleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsApplicable(Note note)
|
private NoteWithVisibilities? IsApplicable(Note note)
|
||||||
|
{
|
||||||
|
if (!IsApplicableBool(note)) return null;
|
||||||
|
var res = EnforceRenoteReplyVisibility(note);
|
||||||
|
return res is not { Note.IsPureRenote: true, Renote: null } ? null : res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsApplicableBool(Note note)
|
||||||
{
|
{
|
||||||
if (note.Visibility != Note.NoteVisibility.Public) return false;
|
if (note.Visibility != Note.NoteVisibility.Public) return false;
|
||||||
if (!local && note.UserHost == null) return false;
|
if (!local && note.UserHost == null) return false;
|
||||||
if (!remote && note.UserHost != null) return false;
|
if (!remote && note.UserHost != null) return false;
|
||||||
if (onlyMedia && note.FileIds.Count == 0) return false;
|
return !onlyMedia || note.FileIds.Count != 0;
|
||||||
return EnforceRenoteReplyVisibility(note) is not { IsPureRenote: true, Renote: null };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Note EnforceRenoteReplyVisibility(Note note)
|
private NoteWithVisibilities EnforceRenoteReplyVisibility(Note note)
|
||||||
{
|
{
|
||||||
if (note.Renote?.IsVisibleFor(connection.Token.User, connection.Following) ?? false)
|
var wrapped = new NoteWithVisibilities(note);
|
||||||
note.Renote = null;
|
if (wrapped.Renote?.IsVisibleFor(connection.Token.User, connection.Following) ?? false)
|
||||||
if (note.Reply?.IsVisibleFor(connection.Token.User, connection.Following) ?? false)
|
wrapped.Renote = null;
|
||||||
note.Reply = null;
|
|
||||||
|
|
||||||
return note;
|
return wrapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NoteWithVisibilities(Note note)
|
||||||
|
{
|
||||||
|
public readonly Note Note = note;
|
||||||
|
public Note? Renote = note.Renote;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StatusEntity EnforceRenoteReplyVisibility(StatusEntity rendered, NoteWithVisibilities note)
|
||||||
|
{
|
||||||
|
var renote = note.Renote == null && rendered.Renote != null;
|
||||||
|
if (!renote) return rendered;
|
||||||
|
|
||||||
|
rendered = (StatusEntity)rendered.Clone();
|
||||||
|
if (renote) rendered.Renote = null;
|
||||||
|
return rendered;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsFiltered(Note note) => connection.IsFiltered(note.User) ||
|
private bool IsFiltered(Note note) => connection.IsFiltered(note.User) ||
|
||||||
|
@ -73,13 +95,14 @@ public class PublicChannel(
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!IsApplicable(note)) return;
|
var wrapped = IsApplicable(note);
|
||||||
|
if (wrapped == null) return;
|
||||||
if (IsFiltered(note)) return;
|
if (IsFiltered(note)) return;
|
||||||
await using var scope = connection.ScopeFactory.CreateAsyncScope();
|
await using var scope = connection.ScopeFactory.CreateAsyncScope();
|
||||||
|
|
||||||
var provider = scope.ServiceProvider;
|
var renderer = scope.ServiceProvider.GetRequiredService<NoteRenderer>();
|
||||||
var renderer = provider.GetRequiredService<NoteRenderer>();
|
var intermediate = await renderer.RenderAsync(note, connection.Token.User);
|
||||||
var rendered = await renderer.RenderAsync(note, connection.Token.User);
|
var rendered = EnforceRenoteReplyVisibility(intermediate, wrapped);
|
||||||
var message = new StreamingUpdateMessage
|
var message = new StreamingUpdateMessage
|
||||||
{
|
{
|
||||||
Stream = [Name],
|
Stream = [Name],
|
||||||
|
@ -90,7 +113,7 @@ public class PublicChannel(
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.LogError("Event handler OnNotePublished threw exception: {e}", e);
|
_logger.LogError("Event handler OnNotePublished threw exception: {e}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,13 +121,14 @@ public class PublicChannel(
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!IsApplicable(note)) return;
|
var wrapped = IsApplicable(note);
|
||||||
|
if (wrapped == null) return;
|
||||||
if (IsFiltered(note)) return;
|
if (IsFiltered(note)) return;
|
||||||
await using var scope = connection.ScopeFactory.CreateAsyncScope();
|
await using var scope = connection.ScopeFactory.CreateAsyncScope();
|
||||||
|
|
||||||
var provider = scope.ServiceProvider;
|
var renderer = scope.ServiceProvider.GetRequiredService<NoteRenderer>();
|
||||||
var renderer = provider.GetRequiredService<NoteRenderer>();
|
var intermediate = await renderer.RenderAsync(note, connection.Token.User);
|
||||||
var rendered = await renderer.RenderAsync(note, connection.Token.User);
|
var rendered = EnforceRenoteReplyVisibility(intermediate, wrapped);
|
||||||
var message = new StreamingUpdateMessage
|
var message = new StreamingUpdateMessage
|
||||||
{
|
{
|
||||||
Stream = [Name],
|
Stream = [Name],
|
||||||
|
@ -115,7 +139,7 @@ public class PublicChannel(
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.LogError("Event handler OnNoteUpdated threw exception: {e}", e);
|
_logger.LogError("Event handler OnNoteUpdated threw exception: {e}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +147,7 @@ public class PublicChannel(
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!IsApplicable(note)) return;
|
if (!IsApplicableBool(note)) return;
|
||||||
if (IsFiltered(note)) return;
|
if (IsFiltered(note)) return;
|
||||||
var message = new StreamingUpdateMessage
|
var message = new StreamingUpdateMessage
|
||||||
{
|
{
|
||||||
|
@ -135,7 +159,7 @@ public class PublicChannel(
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.LogError("Event handler OnNoteDeleted threw exception: {e}", e);
|
_logger.LogError("Event handler OnNoteDeleted threw exception: {e}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,14 +3,13 @@ using Iceshrimp.Backend.Controllers.Mastodon.Renderers;
|
||||||
using Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
|
using Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
|
||||||
using Iceshrimp.Backend.Core.Database;
|
using Iceshrimp.Backend.Core.Database;
|
||||||
using Iceshrimp.Backend.Core.Database.Tables;
|
using Iceshrimp.Backend.Core.Database.Tables;
|
||||||
using Iceshrimp.Backend.Core.Events;
|
|
||||||
using Iceshrimp.Backend.Core.Middleware;
|
using Iceshrimp.Backend.Core.Middleware;
|
||||||
|
|
||||||
namespace Iceshrimp.Backend.Controllers.Mastodon.Streaming.Channels;
|
namespace Iceshrimp.Backend.Controllers.Mastodon.Streaming.Channels;
|
||||||
|
|
||||||
public class UserChannel(WebSocketConnection connection, bool notificationsOnly) : IChannel
|
public class UserChannel(WebSocketConnection connection, bool notificationsOnly) : IChannel
|
||||||
{
|
{
|
||||||
public readonly ILogger<UserChannel> Logger =
|
private readonly ILogger<UserChannel> _logger =
|
||||||
connection.Scope.ServiceProvider.GetRequiredService<ILogger<UserChannel>>();
|
connection.Scope.ServiceProvider.GetRequiredService<ILogger<UserChannel>>();
|
||||||
|
|
||||||
public string Name => notificationsOnly ? "user:notification" : "user";
|
public string Name => notificationsOnly ? "user:notification" : "user";
|
||||||
|
@ -27,9 +26,9 @@ public class UserChannel(WebSocketConnection connection, bool notificationsOnly)
|
||||||
|
|
||||||
if (!notificationsOnly)
|
if (!notificationsOnly)
|
||||||
{
|
{
|
||||||
connection.EventService.NotePublished += OnNotePublished;
|
connection.EventService.NotePublished += OnNotePublished;
|
||||||
connection.EventService.NoteUpdated += OnNoteUpdated;
|
connection.EventService.NoteUpdated += OnNoteUpdated;
|
||||||
connection.EventService.NoteDeleted += OnNoteDeleted;
|
connection.EventService.NoteDeleted += OnNoteDeleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
connection.EventService.Notification += OnNotification;
|
connection.EventService.Notification += OnNotification;
|
||||||
|
@ -47,23 +46,26 @@ public class UserChannel(WebSocketConnection connection, bool notificationsOnly)
|
||||||
{
|
{
|
||||||
if (!notificationsOnly)
|
if (!notificationsOnly)
|
||||||
{
|
{
|
||||||
connection.EventService.NotePublished -= OnNotePublished;
|
connection.EventService.NotePublished -= OnNotePublished;
|
||||||
connection.EventService.NoteUpdated -= OnNoteUpdated;
|
connection.EventService.NoteUpdated -= OnNoteUpdated;
|
||||||
connection.EventService.NoteDeleted -= OnNoteDeleted;
|
connection.EventService.NoteDeleted -= OnNoteDeleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
connection.EventService.Notification -= OnNotification;
|
connection.EventService.Notification -= OnNotification;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsApplicable(Note note) =>
|
private NoteWithVisibilities? IsApplicable(Note note)
|
||||||
connection.Following.Prepend(connection.Token.User.Id).Contains(note.UserId) &&
|
{
|
||||||
EnforceRenoteReplyVisibility(note) is not { IsPureRenote: true, Renote: null };
|
if (IsApplicableBool(note)) return null;
|
||||||
|
var res = EnforceRenoteReplyVisibility(note);
|
||||||
|
return res is not { Note.IsPureRenote: true, Renote: null } ? null : res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsApplicableBool(Note note) =>
|
||||||
|
connection.Following.Prepend(connection.Token.User.Id).Contains(note.UserId);
|
||||||
|
|
||||||
private bool IsApplicable(Notification notification) => notification.NotifieeId == connection.Token.User.Id;
|
private bool IsApplicable(Notification notification) => notification.NotifieeId == connection.Token.User.Id;
|
||||||
|
|
||||||
private bool IsApplicable(UserInteraction interaction) => interaction.Actor.Id == connection.Token.User.Id ||
|
|
||||||
interaction.Object.Id == connection.Token.User.Id;
|
|
||||||
|
|
||||||
private bool IsFiltered(Note note) => connection.IsFiltered(note.User) ||
|
private bool IsFiltered(Note note) => connection.IsFiltered(note.User) ||
|
||||||
(note.Renote?.User != null && connection.IsFiltered(note.Renote.User)) ||
|
(note.Renote?.User != null && connection.IsFiltered(note.Renote.User)) ||
|
||||||
note.Renote?.Renote?.User != null &&
|
note.Renote?.Renote?.User != null &&
|
||||||
|
@ -73,26 +75,43 @@ public class UserChannel(WebSocketConnection connection, bool notificationsOnly)
|
||||||
(notification.Notifier != null && connection.IsFiltered(notification.Notifier)) ||
|
(notification.Notifier != null && connection.IsFiltered(notification.Notifier)) ||
|
||||||
(notification.Note != null && IsFiltered(notification.Note));
|
(notification.Note != null && IsFiltered(notification.Note));
|
||||||
|
|
||||||
private Note EnforceRenoteReplyVisibility(Note note)
|
private NoteWithVisibilities EnforceRenoteReplyVisibility(Note note)
|
||||||
{
|
{
|
||||||
if (note.Renote?.IsVisibleFor(connection.Token.User, connection.Following) ?? false)
|
var wrapped = new NoteWithVisibilities(note);
|
||||||
note.Renote = null;
|
if (wrapped.Renote?.IsVisibleFor(connection.Token.User, connection.Following) ?? false)
|
||||||
if (note.Reply?.IsVisibleFor(connection.Token.User, connection.Following) ?? false)
|
wrapped.Renote = null;
|
||||||
note.Reply = null;
|
|
||||||
|
|
||||||
return note;
|
return wrapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NoteWithVisibilities(Note note)
|
||||||
|
{
|
||||||
|
public readonly Note Note = note;
|
||||||
|
public Note? Renote = note.Renote;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StatusEntity EnforceRenoteReplyVisibility(StatusEntity rendered, NoteWithVisibilities note)
|
||||||
|
{
|
||||||
|
var renote = note.Renote == null && rendered.Renote != null;
|
||||||
|
if (!renote) return rendered;
|
||||||
|
|
||||||
|
rendered = (StatusEntity)rendered.Clone();
|
||||||
|
if (renote) rendered.Renote = null;
|
||||||
|
return rendered;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnNotePublished(object? _, Note note)
|
private async void OnNotePublished(object? _, Note note)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!IsApplicable(note)) return;
|
var wrapped = IsApplicable(note);
|
||||||
|
if (wrapped == null) return;
|
||||||
if (IsFiltered(note)) return;
|
if (IsFiltered(note)) return;
|
||||||
await using var scope = connection.ScopeFactory.CreateAsyncScope();
|
await using var scope = connection.ScopeFactory.CreateAsyncScope();
|
||||||
|
|
||||||
var renderer = scope.ServiceProvider.GetRequiredService<NoteRenderer>();
|
var renderer = scope.ServiceProvider.GetRequiredService<NoteRenderer>();
|
||||||
var rendered = await renderer.RenderAsync(note, connection.Token.User);
|
var intermediate = await renderer.RenderAsync(note, connection.Token.User);
|
||||||
|
var rendered = EnforceRenoteReplyVisibility(intermediate, wrapped);
|
||||||
var message = new StreamingUpdateMessage
|
var message = new StreamingUpdateMessage
|
||||||
{
|
{
|
||||||
Stream = [Name],
|
Stream = [Name],
|
||||||
|
@ -103,7 +122,7 @@ public class UserChannel(WebSocketConnection connection, bool notificationsOnly)
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.LogError("Event handler OnNoteUpdated threw exception: {e}", e);
|
_logger.LogError("Event handler OnNoteUpdated threw exception: {e}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,12 +130,14 @@ public class UserChannel(WebSocketConnection connection, bool notificationsOnly)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!IsApplicable(note)) return;
|
var wrapped = IsApplicable(note);
|
||||||
|
if (wrapped == null) return;
|
||||||
if (IsFiltered(note)) return;
|
if (IsFiltered(note)) return;
|
||||||
await using var scope = connection.ScopeFactory.CreateAsyncScope();
|
await using var scope = connection.ScopeFactory.CreateAsyncScope();
|
||||||
|
|
||||||
var renderer = scope.ServiceProvider.GetRequiredService<NoteRenderer>();
|
var renderer = scope.ServiceProvider.GetRequiredService<NoteRenderer>();
|
||||||
var rendered = await renderer.RenderAsync(note, connection.Token.User);
|
var intermediate = await renderer.RenderAsync(note, connection.Token.User);
|
||||||
|
var rendered = EnforceRenoteReplyVisibility(intermediate, wrapped);
|
||||||
var message = new StreamingUpdateMessage
|
var message = new StreamingUpdateMessage
|
||||||
{
|
{
|
||||||
Stream = [Name],
|
Stream = [Name],
|
||||||
|
@ -127,7 +148,7 @@ public class UserChannel(WebSocketConnection connection, bool notificationsOnly)
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.LogError("Event handler OnNoteUpdated threw exception: {e}", e);
|
_logger.LogError("Event handler OnNoteUpdated threw exception: {e}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +156,7 @@ public class UserChannel(WebSocketConnection connection, bool notificationsOnly)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!IsApplicable(note)) return;
|
if (!IsApplicableBool(note)) return;
|
||||||
if (IsFiltered(note)) return;
|
if (IsFiltered(note)) return;
|
||||||
var message = new StreamingUpdateMessage
|
var message = new StreamingUpdateMessage
|
||||||
{
|
{
|
||||||
|
@ -147,7 +168,7 @@ public class UserChannel(WebSocketConnection connection, bool notificationsOnly)
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.LogError("Event handler OnNoteDeleted threw exception: {e}", e);
|
_logger.LogError("Event handler OnNoteDeleted threw exception: {e}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,7 +203,7 @@ public class UserChannel(WebSocketConnection connection, bool notificationsOnly)
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.LogError("Event handler OnNotification threw exception: {e}", e);
|
_logger.LogError("Event handler OnNotification threw exception: {e}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -138,13 +138,13 @@ public sealed class StreamingConnectionAggregate : IDisposable
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!IsApplicable(data.note)) return;
|
var wrapped = IsApplicable(data.note);
|
||||||
|
if (wrapped == null) return;
|
||||||
var recipients = FindRecipients(data.note);
|
var recipients = FindRecipients(data.note);
|
||||||
if (recipients.connectionIds.Count == 0) return;
|
if (recipients.connectionIds.Count == 0) return;
|
||||||
|
|
||||||
await _hub.Clients
|
var rendered = EnforceRenoteReplyVisibility(await data.rendered(), wrapped);
|
||||||
.Clients(recipients.connectionIds)
|
await _hub.Clients.Clients(recipients.connectionIds).NotePublished(recipients.timelines, rendered);
|
||||||
.NotePublished(recipients.timelines, await data.rendered());
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -156,13 +156,13 @@ public sealed class StreamingConnectionAggregate : IDisposable
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!IsApplicable(data.note)) return;
|
var wrapped = IsApplicable(data.note);
|
||||||
|
if (wrapped == null) return;
|
||||||
var recipients = FindRecipients(data.note);
|
var recipients = FindRecipients(data.note);
|
||||||
if (recipients.connectionIds.Count == 0) return;
|
if (recipients.connectionIds.Count == 0) return;
|
||||||
|
|
||||||
await _hub.Clients
|
var rendered = EnforceRenoteReplyVisibility(await data.rendered(), wrapped);
|
||||||
.Clients(recipients.connectionIds)
|
await _hub.Clients.Clients(recipients.connectionIds).NoteUpdated(recipients.timelines, rendered);
|
||||||
.NoteUpdated(recipients.timelines, await data.rendered());
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -170,16 +170,29 @@ public sealed class StreamingConnectionAggregate : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsApplicable(Note note)
|
private static NoteResponse EnforceRenoteReplyVisibility(NoteResponse rendered, NoteWithVisibilities note)
|
||||||
{
|
{
|
||||||
if (_subscriptions.IsEmpty) return false;
|
var renote = note.Renote == null && rendered.Renote != null;
|
||||||
if (!note.IsVisibleFor(_user, _following)) return false;
|
var reply = note.Reply == null && rendered.Reply != null;
|
||||||
if (note.Visibility != Note.NoteVisibility.Public && !IsFollowingOrSelf(note.User)) return false;
|
if (!renote && !reply) return rendered;
|
||||||
if (IsFiltered(note.User)) return false;
|
|
||||||
if (note.Reply != null && IsFiltered(note.Reply.User)) return false;
|
|
||||||
if (note.Renote != null && IsFiltered(note.Renote.User)) return false;
|
|
||||||
|
|
||||||
return EnforceRenoteReplyVisibility(note) is not { IsPureRenote: true, Renote: null };
|
rendered = (NoteResponse)rendered.Clone();
|
||||||
|
if (renote) rendered.Renote = null;
|
||||||
|
if (reply) rendered.Reply = null;
|
||||||
|
return rendered;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NoteWithVisibilities? IsApplicable(Note note)
|
||||||
|
{
|
||||||
|
if (_subscriptions.IsEmpty) return null;
|
||||||
|
if (!note.IsVisibleFor(_user, _following)) return null;
|
||||||
|
if (note.Visibility != Note.NoteVisibility.Public && !IsFollowingOrSelf(note.User)) return null;
|
||||||
|
if (IsFiltered(note.User)) return null;
|
||||||
|
if (note.Reply != null && IsFiltered(note.Reply.User)) return null;
|
||||||
|
if (note.Renote != null && IsFiltered(note.Renote.User)) return null;
|
||||||
|
|
||||||
|
var res = EnforceRenoteReplyVisibility(note);
|
||||||
|
return res is not { Note.IsPureRenote: true, Renote: null } ? null : res;
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameter")]
|
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameter")]
|
||||||
|
@ -193,14 +206,22 @@ public sealed class StreamingConnectionAggregate : IDisposable
|
||||||
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameter")]
|
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameter")]
|
||||||
private bool IsFollowingOrSelf(User user) => user.Id == _userId || _following.Contains(user.Id);
|
private bool IsFollowingOrSelf(User user) => user.Id == _userId || _following.Contains(user.Id);
|
||||||
|
|
||||||
private Note EnforceRenoteReplyVisibility(Note note)
|
private NoteWithVisibilities EnforceRenoteReplyVisibility(Note note)
|
||||||
{
|
{
|
||||||
if (note.Renote?.IsVisibleFor(_user, _following) ?? false)
|
var wrapped = new NoteWithVisibilities(note);
|
||||||
note.Renote = null;
|
if (wrapped.Renote?.IsVisibleFor(_user, _following) ?? false)
|
||||||
if (note.Reply?.IsVisibleFor(_user, _following) ?? false)
|
wrapped.Renote = null;
|
||||||
note.Reply = null;
|
if (wrapped.Reply?.IsVisibleFor(_user, _following) ?? false)
|
||||||
|
wrapped.Reply = null;
|
||||||
|
|
||||||
return note;
|
return wrapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NoteWithVisibilities(Note note)
|
||||||
|
{
|
||||||
|
public readonly Note Note = note;
|
||||||
|
public Note? Reply = note.Reply;
|
||||||
|
public Note? Renote = note.Renote;
|
||||||
}
|
}
|
||||||
|
|
||||||
private (List<string> connectionIds, List<StreamingTimeline> timelines) FindRecipients(Note note)
|
private (List<string> connectionIds, List<StreamingTimeline> timelines) FindRecipients(Note note)
|
||||||
|
|
|
@ -4,7 +4,7 @@ using JI = System.Text.Json.Serialization.JsonIgnoreAttribute;
|
||||||
|
|
||||||
namespace Iceshrimp.Shared.Schemas;
|
namespace Iceshrimp.Shared.Schemas;
|
||||||
|
|
||||||
public class NoteResponse : NoteWithQuote
|
public class NoteResponse : NoteWithQuote, ICloneable
|
||||||
{
|
{
|
||||||
[J("reply")] public NoteBase? Reply { get; set; }
|
[J("reply")] public NoteBase? Reply { get; set; }
|
||||||
[J("replyId")] public string? ReplyId { get; set; }
|
[J("replyId")] public string? ReplyId { get; set; }
|
||||||
|
@ -18,6 +18,8 @@ public class NoteResponse : NoteWithQuote
|
||||||
[JI(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
[JI(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
[J("descendants")]
|
[J("descendants")]
|
||||||
public List<NoteResponse>? Descendants { get; set; }
|
public List<NoteResponse>? Descendants { get; set; }
|
||||||
|
|
||||||
|
public object Clone() => MemberwiseClone();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class NoteWithQuote : NoteBase
|
public class NoteWithQuote : NoteBase
|
||||||
|
|
Loading…
Add table
Reference in a new issue