diff --git a/Iceshrimp.Backend/Controllers/Mastodon/Streaming/Channels/PublicChannel.cs b/Iceshrimp.Backend/Controllers/Mastodon/Streaming/Channels/PublicChannel.cs index 8ca2c3ce..3b426883 100644 --- a/Iceshrimp.Backend/Controllers/Mastodon/Streaming/Channels/PublicChannel.cs +++ b/Iceshrimp.Backend/Controllers/Mastodon/Streaming/Channels/PublicChannel.cs @@ -51,8 +51,17 @@ public class PublicChannel( if (!local && note.UserHost == null) return false; if (!remote && note.UserHost != null) return false; if (onlyMedia && note.FileIds.Count == 0) return false; + return EnforceRenoteReplyVisibility(note) is not { IsPureRenote: true, Renote: null }; + } + + private Note EnforceRenoteReplyVisibility(Note note) + { + if (note.Renote?.IsVisibleFor(connection.Token.User, connection.Following) ?? false) + note.Renote = null; + if (note.Reply?.IsVisibleFor(connection.Token.User, connection.Following) ?? false) + note.Reply = null; - return true; + return note; } private bool IsFiltered(Note note) => connection.IsFiltered(note.User) || diff --git a/Iceshrimp.Backend/Controllers/Mastodon/Streaming/Channels/UserChannel.cs b/Iceshrimp.Backend/Controllers/Mastodon/Streaming/Channels/UserChannel.cs index 0207b8c2..fe74e24f 100644 --- a/Iceshrimp.Backend/Controllers/Mastodon/Streaming/Channels/UserChannel.cs +++ b/Iceshrimp.Backend/Controllers/Mastodon/Streaming/Channels/UserChannel.cs @@ -5,7 +5,6 @@ using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Events; using Iceshrimp.Backend.Core.Middleware; -using Microsoft.EntityFrameworkCore; namespace Iceshrimp.Backend.Controllers.Mastodon.Streaming.Channels; @@ -14,10 +13,9 @@ public class UserChannel(WebSocketConnection connection, bool notificationsOnly) public readonly ILogger Logger = connection.Scope.ServiceProvider.GetRequiredService>(); - private List _followedUsers = []; - public string Name => notificationsOnly ? "user:notification" : "user"; - public List Scopes => ["read:statuses", "read:notifications"]; - public bool IsSubscribed { get; private set; } + public string Name => notificationsOnly ? "user:notification" : "user"; + public List Scopes => ["read:statuses", "read:notifications"]; + public bool IsSubscribed { get; private set; } public async Task Subscribe(StreamingRequestMessage _) { @@ -29,17 +27,9 @@ public class UserChannel(WebSocketConnection connection, bool notificationsOnly) if (!notificationsOnly) { - _followedUsers = await db.Users.Where(p => p == connection.Token.User) - .SelectMany(p => p.Following) - .Select(p => p.Id) - .ToListAsync(); - connection.EventService.NotePublished += OnNotePublished; connection.EventService.NoteUpdated += OnNoteUpdated; connection.EventService.NoteDeleted += OnNoteDeleted; - connection.EventService.UserFollowed += OnRelationChange; - connection.EventService.UserUnfollowed += OnRelationChange; - connection.EventService.UserBlocked += OnRelationChange; } connection.EventService.Notification += OnNotification; @@ -60,15 +50,15 @@ public class UserChannel(WebSocketConnection connection, bool notificationsOnly) connection.EventService.NotePublished -= OnNotePublished; connection.EventService.NoteUpdated -= OnNoteUpdated; connection.EventService.NoteDeleted -= OnNoteDeleted; - connection.EventService.UserFollowed -= OnRelationChange; - connection.EventService.UserUnfollowed -= OnRelationChange; - connection.EventService.UserBlocked -= OnRelationChange; } connection.EventService.Notification -= OnNotification; } - private bool IsApplicable(Note note) => _followedUsers.Prepend(connection.Token.User.Id).Contains(note.UserId); + private bool IsApplicable(Note note) => + connection.Following.Prepend(connection.Token.User.Id).Contains(note.UserId) && + EnforceRenoteReplyVisibility(note) is not { IsPureRenote: true, Renote: null }; + private bool IsApplicable(Notification notification) => notification.NotifieeId == connection.Token.User.Id; private bool IsApplicable(UserInteraction interaction) => interaction.Actor.Id == connection.Token.User.Id || @@ -82,6 +72,16 @@ public class UserChannel(WebSocketConnection connection, bool notificationsOnly) private bool IsFiltered(Notification notification) => (notification.Notifier != null && connection.IsFiltered(notification.Notifier)) || (notification.Note != null && IsFiltered(notification.Note)); + + private Note EnforceRenoteReplyVisibility(Note note) + { + if (note.Renote?.IsVisibleFor(connection.Token.User, connection.Following) ?? false) + note.Renote = null; + if (note.Reply?.IsVisibleFor(connection.Token.User, connection.Following) ?? false) + note.Reply = null; + + return note; + } private async void OnNotePublished(object? _, Note note) { @@ -185,22 +185,4 @@ public class UserChannel(WebSocketConnection connection, bool notificationsOnly) Logger.LogError("Event handler OnNotification threw exception: {e}", e); } } - - private async void OnRelationChange(object? _, UserInteraction interaction) - { - try - { - if (!IsApplicable(interaction)) return; - await using var scope = connection.ScopeFactory.CreateAsyncScope(); - await using var db = scope.ServiceProvider.GetRequiredService(); - _followedUsers = await db.Users.Where(p => p == connection.Token.User) - .SelectMany(p => p.Following) - .Select(p => p.Id) - .ToListAsync(); - } - catch (Exception e) - { - Logger.LogError("Event handler OnRelationChange threw exception: {e}", e); - } - } } \ No newline at end of file diff --git a/Iceshrimp.Backend/Controllers/Mastodon/Streaming/WebSocketConnection.cs b/Iceshrimp.Backend/Controllers/Mastodon/Streaming/WebSocketConnection.cs index 0cbdac5d..ca1e1312 100644 --- a/Iceshrimp.Backend/Controllers/Mastodon/Streaming/WebSocketConnection.cs +++ b/Iceshrimp.Backend/Controllers/Mastodon/Streaming/WebSocketConnection.cs @@ -3,10 +3,12 @@ using System.Net.WebSockets; using System.Text; using System.Text.Json; using Iceshrimp.Backend.Controllers.Mastodon.Streaming.Channels; +using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Events; using Iceshrimp.Backend.Core.Helpers; using Iceshrimp.Backend.Core.Services; +using Microsoft.EntityFrameworkCore; namespace Iceshrimp.Backend.Controllers.Mastodon.Streaming; @@ -24,6 +26,7 @@ public sealed class WebSocketConnection( public readonly IServiceScope Scope = scopeFactory.CreateScope(); public readonly IServiceScopeFactory ScopeFactory = scopeFactory; public readonly OauthToken Token = token; + public readonly WriteLockingList Following = []; private readonly WriteLockingList _blocking = []; private readonly WriteLockingList _blockedBy = []; private readonly WriteLockingList _mutedUsers = []; @@ -54,10 +57,23 @@ public sealed class WebSocketConnection( Channels.Add(new PublicChannel(this, "public:remote", false, true, false)); Channels.Add(new PublicChannel(this, "public:remote:media", false, true, true)); - EventService.UserBlocked += OnUserUnblock; - EventService.UserUnblocked += OnUserBlock; - EventService.UserMuted += OnUserMute; - EventService.UserUnmuted += OnUserUnmute; + EventService.UserBlocked += OnUserUnblock; + EventService.UserUnblocked += OnUserBlock; + EventService.UserMuted += OnUserMute; + EventService.UserUnmuted += OnUserUnmute; + EventService.UserFollowed -= OnUserFollow; + EventService.UserUnfollowed -= OnUserUnfollow; + + _ = InitializeFollowing(); + } + + private async Task InitializeFollowing() + { + await using var db = Scope.ServiceProvider.GetRequiredService(); + Following.AddRange(await db.Users.Where(p => p == Token.User) + .SelectMany(p => p.Following) + .Select(p => p.Id) + .ToListAsync()); } public async Task HandleSocketMessageAsync(string payload) @@ -184,6 +200,34 @@ public sealed class WebSocketConnection( logger.LogError("Event handler OnUserUnmute threw exception: {e}", e); } } + + private void OnUserFollow(object? _, UserInteraction interaction) + { + try + { + if (interaction.Actor.Id == Token.User.Id) + Following.Add(interaction.Object.Id); + } + catch (Exception e) + { + var logger = Scope.ServiceProvider.GetRequiredService>(); + logger.LogError("Event handler OnUserFollow threw exception: {e}", e); + } + } + + private void OnUserUnfollow(object? _, UserInteraction interaction) + { + try + { + if (interaction.Actor.Id == Token.User.Id) + Following.Remove(interaction.Object.Id); + } + catch (Exception e) + { + var logger = Scope.ServiceProvider.GetRequiredService>(); + logger.LogError("Event handler OnUserUnfollow threw exception: {e}", e); + } + } [SuppressMessage("ReSharper", "SuggestBaseTypeForParameter")] public bool IsFiltered(User user) =>