diff --git a/Iceshrimp.Backend/Controllers/NoteController.cs b/Iceshrimp.Backend/Controllers/NoteController.cs index 96fbe854..fb9eb8c1 100644 --- a/Iceshrimp.Backend/Controllers/NoteController.cs +++ b/Iceshrimp.Backend/Controllers/NoteController.cs @@ -69,7 +69,8 @@ public class NoteController( .PrecomputeNoteContextVisibilities(user) .ToListAsync(); - return Ok(await noteRenderer.RenderMany(notes.EnforceRenoteReplyVisibility(), user)); + return Ok(await noteRenderer.RenderMany(notes.EnforceRenoteReplyVisibility(), user, + Filter.FilterContext.Threads)); } [HttpGet("{id}/descendants")] @@ -97,7 +98,8 @@ public class NoteController( .PrecomputeNoteContextVisibilities(user) .ToListAsync(); - return Ok(await noteRenderer.RenderMany(notes.EnforceRenoteReplyVisibility(), user)); + return Ok(await noteRenderer.RenderMany(notes.EnforceRenoteReplyVisibility(), user, + Filter.FilterContext.Threads)); } [HttpGet("{id}/reactions/{name}")] diff --git a/Iceshrimp.Backend/Controllers/Renderers/NoteRenderer.cs b/Iceshrimp.Backend/Controllers/Renderers/NoteRenderer.cs index 9688eac8..a71e74dc 100644 --- a/Iceshrimp.Backend/Controllers/Renderers/NoteRenderer.cs +++ b/Iceshrimp.Backend/Controllers/Renderers/NoteRenderer.cs @@ -1,7 +1,8 @@ - using Iceshrimp.Shared.Schemas; +using Iceshrimp.Shared.Schemas; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Extensions; +using Iceshrimp.Backend.Core.Helpers; using Iceshrimp.Backend.Core.Services; using Microsoft.EntityFrameworkCore; @@ -9,13 +10,28 @@ namespace Iceshrimp.Backend.Controllers.Renderers; public class NoteRenderer(UserRenderer userRenderer, DatabaseContext db, EmojiService emojiSvc) { - public async Task RenderOne(Note note, User? localUser, NoteRendererDto? data = null) + public async Task RenderOne( + Note note, User? user, Filter.FilterContext? filterContext = null, NoteRendererDto? data = null + ) { - var res = await RenderBaseInternal(note, localUser, data); + var res = await RenderBaseInternal(note, user, data); - var renote = note.Renote is { IsPureRenote: true } ? await RenderRenote(note.Renote, localUser, data) : null; - var quote = note.Renote is { IsPureRenote: false } ? await RenderBase(note.Renote, localUser, data) : null; - var reply = note.Reply != null ? await RenderBase(note.Reply, localUser, data) : null; + var renote = note.Renote is { IsPureRenote: true } ? await RenderRenote(note.Renote, user, data) : null; + var quote = note.Renote is { IsPureRenote: false } ? await RenderBase(note.Renote, user, data) : null; + var reply = note.Reply != null ? await RenderBase(note.Reply, user, data) : null; + + var filters = data?.Filters ?? await GetFilters(user, filterContext); + var filtered = FilterHelper.IsFiltered([note, note.Reply, note.Renote, note.Renote?.Renote], filters); + + if (filtered.HasValue) + { + res.Filtered = new NoteFilteredSchema + { + Id = filtered.Value.filter.Id, + Keyword = filtered.Value.keyword, + Hide = filtered.Value.filter.Action == Filter.FilterAction.Hide + }; + } res.Renote = renote; res.RenoteId = note.RenoteId; @@ -27,10 +43,10 @@ public class NoteRenderer(UserRenderer userRenderer, DatabaseContext db, EmojiSe return res; } - private async Task RenderRenote(Note note, User? localUser, NoteRendererDto? data = null) + private async Task RenderRenote(Note note, User? user, NoteRendererDto? data = null) { - var res = await RenderBaseInternal(note, localUser, data); - var quote = note.Renote is { IsPureRenote: false } ? await RenderBase(note.Renote, localUser, data) : null; + var res = await RenderBaseInternal(note, user, data); + var quote = note.Renote is { IsPureRenote: false } ? await RenderBase(note.Renote, user, data) : null; res.Quote = quote; res.QuoteId = note.RenoteId; @@ -41,11 +57,11 @@ public class NoteRenderer(UserRenderer userRenderer, DatabaseContext db, EmojiSe private async Task RenderBase(Note note, User? localUser, NoteRendererDto? data = null) => await RenderBaseInternal(note, localUser, data); - private async Task RenderBaseInternal(Note note, User? localUser, NoteRendererDto? data = null) + private async Task RenderBaseInternal(Note note, User? user, NoteRendererDto? data = null) { - var user = (data?.Users ?? await GetUsers([note])).First(p => p.Id == note.User.Id); + var noteUser = (data?.Users ?? await GetUsers([note])).First(p => p.Id == note.User.Id); var attachments = (data?.Attachments ?? await GetAttachments([note])).Where(p => note.FileIds.Contains(p.Id)); - var reactions = (data?.Reactions ?? await GetReactions([note], localUser)).Where(p => p.NoteId == note.Id); + var reactions = (data?.Reactions ?? await GetReactions([note], user)).Where(p => p.NoteId == note.Id); return new NoteResponse { @@ -54,7 +70,7 @@ public class NoteRenderer(UserRenderer userRenderer, DatabaseContext db, EmojiSe Text = note.Text, Cw = note.Cw, Visibility = RenderVisibility(note.Visibility), - User = user, + User = noteUser, Attachments = attachments.ToList(), Reactions = reactions.ToList() }; @@ -130,19 +146,28 @@ public class NoteRenderer(UserRenderer userRenderer, DatabaseContext db, EmojiSe .ToList(); } - public async Task> RenderMany(IEnumerable notes, User? user) + private async Task> GetFilters(User? user, Filter.FilterContext? filterContext) + { + if (filterContext == null) return []; + return await db.Filters.Where(p => p.User == user && p.Contexts.Contains(filterContext.Value)).ToListAsync(); + } + + public async Task> RenderMany( + IEnumerable notes, User? user, Filter.FilterContext? filterContext = null + ) { var notesList = notes.ToList(); if (notesList.Count == 0) return []; - var allNotes = GetAllNotes(notesList); + var allNotes = GetAllNotes(notesList); var data = new NoteRendererDto { Users = await GetUsers(allNotes), Attachments = await GetAttachments(allNotes), - Reactions = await GetReactions(allNotes, user) + Reactions = await GetReactions(allNotes, user), + Filters = await GetFilters(user, filterContext) }; - return await notesList.Select(p => RenderOne(p, user, data)).AwaitAllAsync(); + return await notesList.Select(p => RenderOne(p, user, filterContext, data)).AwaitAllAsync(); } public class NoteRendererDto @@ -150,5 +175,6 @@ public class NoteRenderer(UserRenderer userRenderer, DatabaseContext db, EmojiSe public List? Attachments; public List? Reactions; public List? Users; + public List? Filters; } } \ No newline at end of file diff --git a/Iceshrimp.Backend/Controllers/Renderers/NotificationRenderer.cs b/Iceshrimp.Backend/Controllers/Renderers/NotificationRenderer.cs index 7ef8b822..17139a7e 100644 --- a/Iceshrimp.Backend/Controllers/Renderers/NotificationRenderer.cs +++ b/Iceshrimp.Backend/Controllers/Renderers/NotificationRenderer.cs @@ -62,7 +62,7 @@ public class NotificationRenderer( private async Task> GetNotes(IEnumerable notifications, User user) { var notes = notifications.Select(p => p.Note).OfType().DistinctBy(p => p.Id); - return await noteRenderer.RenderMany(notes, user).ToListAsync(); + return await noteRenderer.RenderMany(notes, user, Filter.FilterContext.Notifications).ToListAsync(); } public async Task> RenderMany(IEnumerable notifications, User user) diff --git a/Iceshrimp.Backend/Controllers/TimelineController.cs b/Iceshrimp.Backend/Controllers/TimelineController.cs index 201fa515..47e85bf9 100644 --- a/Iceshrimp.Backend/Controllers/TimelineController.cs +++ b/Iceshrimp.Backend/Controllers/TimelineController.cs @@ -4,6 +4,7 @@ using Iceshrimp.Backend.Controllers.Renderers; using Iceshrimp.Backend.Controllers.Schemas; using Iceshrimp.Shared.Schemas; using Iceshrimp.Backend.Core.Database; +using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Extensions; using Iceshrimp.Backend.Core.Middleware; using Iceshrimp.Backend.Core.Services; @@ -39,6 +40,6 @@ public class TimelineController(DatabaseContext db, CacheService cache, NoteRend .PrecomputeVisibilities(user) .ToListAsync(); - return Ok(await noteRenderer.RenderMany(notes.EnforceRenoteReplyVisibility(), user)); + return Ok(await noteRenderer.RenderMany(notes.EnforceRenoteReplyVisibility(), user, Filter.FilterContext.Home)); } } \ No newline at end of file diff --git a/Iceshrimp.Backend/Controllers/UserController.cs b/Iceshrimp.Backend/Controllers/UserController.cs index 6d4977de..d18bf4d8 100644 --- a/Iceshrimp.Backend/Controllers/UserController.cs +++ b/Iceshrimp.Backend/Controllers/UserController.cs @@ -4,6 +4,7 @@ using Iceshrimp.Backend.Controllers.Renderers; using Iceshrimp.Backend.Controllers.Schemas; using Iceshrimp.Shared.Schemas; using Iceshrimp.Backend.Core.Database; +using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Extensions; using Iceshrimp.Backend.Core.Middleware; using Microsoft.AspNetCore.Mvc; @@ -69,6 +70,7 @@ public class UserController( .Paginate(pq, ControllerContext) .ToListAsync(); - return Ok(await noteRenderer.RenderMany(notes.EnforceRenoteReplyVisibility(), localUser)); + return Ok(await noteRenderer.RenderMany(notes.EnforceRenoteReplyVisibility(), localUser, + Filter.FilterContext.Accounts)); } } \ No newline at end of file diff --git a/Iceshrimp.Shared/Schemas/NoteResponse.cs b/Iceshrimp.Shared/Schemas/NoteResponse.cs index 445073fe..e1a55f37 100644 --- a/Iceshrimp.Shared/Schemas/NoteResponse.cs +++ b/Iceshrimp.Shared/Schemas/NoteResponse.cs @@ -5,10 +5,11 @@ namespace Iceshrimp.Shared.Schemas; public class NoteResponse : NoteWithQuote { - [J("reply")] public NoteBase? Reply { get; set; } - [J("replyId")] public string? ReplyId { get; set; } - [J("renote")] public NoteWithQuote? Renote { get; set; } - [J("renoteId")] public string? RenoteId { get; set; } + [J("reply")] public NoteBase? Reply { get; set; } + [J("replyId")] public string? ReplyId { get; set; } + [J("renote")] public NoteWithQuote? Renote { get; set; } + [J("renoteId")] public string? RenoteId { get; set; } + [J("filtered")] public NoteFilteredSchema? Filtered { get; set; } } public class NoteWithQuote : NoteBase @@ -45,4 +46,11 @@ public class NoteReactionSchema [J("count")] public required int Count { get; set; } [J("reacted")] public required bool Reacted { get; set; } [J("url")] public required string? Url { get; set; } +} + +public class NoteFilteredSchema +{ + [J("filterId")] public required long Id { get; set; } + [J("keyword")] public required string Keyword { get; set; } + [J("drop")] public required bool Hide { get; set; } } \ No newline at end of file