[backend/core] Add basic message rewrite policies (ISH-16)
This commit is contained in:
parent
5f5a0c5c0f
commit
5390990448
5 changed files with 231 additions and 0 deletions
103
Iceshrimp.Backend/Core/Policies/ForceCwRewritePolicy.cs
Normal file
103
Iceshrimp.Backend/Core/Policies/ForceCwRewritePolicy.cs
Normal file
|
@ -0,0 +1,103 @@
|
|||
using System.Text.RegularExpressions;
|
||||
using Iceshrimp.Backend.Core.Database.Tables;
|
||||
using Iceshrimp.Backend.Core.Extensions;
|
||||
using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
|
||||
using Iceshrimp.Backend.Core.Helpers;
|
||||
using Iceshrimp.Backend.Core.Services;
|
||||
|
||||
namespace Iceshrimp.Backend.Core.Policies;
|
||||
|
||||
public class ForceCwRewritePolicy(
|
||||
bool enabled,
|
||||
int priority,
|
||||
string cw,
|
||||
string[] words,
|
||||
string[] instances
|
||||
) : IRewritePolicy
|
||||
{
|
||||
public string Name => nameof(ForceCwRewritePolicy);
|
||||
public bool Enabled => enabled;
|
||||
public int Priority => priority;
|
||||
public IRewritePolicy.HookLocationEnum HookLocation => IRewritePolicy.HookLocationEnum.PreLogic;
|
||||
|
||||
public void Apply(NoteService.NoteCreationData data)
|
||||
{
|
||||
if (IsApplicable(data))
|
||||
data.Cw = Apply(data.Cw);
|
||||
}
|
||||
|
||||
public void Apply(NoteService.NoteUpdateData data)
|
||||
{
|
||||
if (IsApplicable(data))
|
||||
data.Cw = Apply(data.Cw);
|
||||
}
|
||||
|
||||
public void Apply(ASNote note, User actor) { }
|
||||
|
||||
private string Apply(string? oldCw)
|
||||
{
|
||||
if (oldCw == null)
|
||||
return cw;
|
||||
if (oldCw.ToLowerInvariant().Contains(cw.ToLowerInvariant()))
|
||||
return oldCw;
|
||||
return oldCw + $", {cw}";
|
||||
}
|
||||
|
||||
private bool IsApplicable(NoteService.NoteCreationData data)
|
||||
{
|
||||
if (instances.Contains(data.User.Host)) return true;
|
||||
return IsApplicable([
|
||||
data.Text, data.Cw, ..data.Poll?.Choices ?? [], ..data.Attachments?.Select(p => p.Comment) ?? []
|
||||
]);
|
||||
}
|
||||
|
||||
private bool IsApplicable(NoteService.NoteUpdateData data)
|
||||
{
|
||||
if (instances.Contains(data.Note.User.Host)) return true;
|
||||
return IsApplicable([
|
||||
data.Text, data.Cw, ..data.Poll?.Choices ?? [], ..data.Attachments?.Select(p => p.Comment) ?? []
|
||||
]);
|
||||
}
|
||||
|
||||
private bool IsApplicable(IEnumerable<string?> candidates)
|
||||
{
|
||||
foreach (var candidate in candidates.NotNull())
|
||||
{
|
||||
foreach (var keyword in words)
|
||||
{
|
||||
if (keyword.StartsWith('"') && keyword.EndsWith('"'))
|
||||
{
|
||||
var pattern = $@"\b{EfHelpers.EscapeRegexQuery(keyword[1..^1])}\b";
|
||||
var regex = new Regex(pattern, RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(10));
|
||||
|
||||
if (regex.IsMatch(candidate))
|
||||
return true;
|
||||
}
|
||||
else if (candidate.Contains(keyword, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class ForceCwRewritePolicyConfiguration : IPolicyConfiguration<ForceCwRewritePolicy>
|
||||
{
|
||||
private string[] _instances = [];
|
||||
public ForceCwRewritePolicy Apply() => new(Enabled, Priority, Cw, Words, Instances);
|
||||
IPolicy IPolicyConfiguration.Apply() => Apply();
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
public int Priority { get; set; }
|
||||
public string Cw { get; set; } = "forced content warning";
|
||||
public string[] Words { get; set; } = [];
|
||||
|
||||
public string[] Instances
|
||||
{
|
||||
get => _instances;
|
||||
set => _instances = value.Select(p => p.ToLowerInvariant()).ToArray();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
using Iceshrimp.Backend.Core.Database.Tables;
|
||||
using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
|
||||
using Iceshrimp.Backend.Core.Services;
|
||||
|
||||
namespace Iceshrimp.Backend.Core.Policies;
|
||||
|
||||
public class ForceMediaSensitiveRewritePolicy(
|
||||
bool enabled,
|
||||
int priority,
|
||||
string[] instances
|
||||
) : IRewritePolicy
|
||||
{
|
||||
public string Name => nameof(ForceMediaSensitiveRewritePolicy);
|
||||
public bool Enabled => enabled;
|
||||
public int Priority => priority;
|
||||
public IRewritePolicy.HookLocationEnum HookLocation => IRewritePolicy.HookLocationEnum.PreLogic;
|
||||
|
||||
public void Apply(NoteService.NoteCreationData data)
|
||||
{
|
||||
if (IsApplicable(data))
|
||||
Apply(data.Attachments);
|
||||
}
|
||||
|
||||
public void Apply(NoteService.NoteUpdateData data)
|
||||
{
|
||||
if (IsApplicable(data))
|
||||
Apply(data.Attachments);
|
||||
}
|
||||
|
||||
public void Apply(ASNote note, User actor) { }
|
||||
|
||||
private void Apply(IEnumerable<DriveFile>? files)
|
||||
{
|
||||
if (files == null) return;
|
||||
foreach (var file in files) file.IsSensitive = true;
|
||||
}
|
||||
|
||||
private bool IsApplicable(NoteService.NoteCreationData data) => instances.Contains(data.User.Host);
|
||||
private bool IsApplicable(NoteService.NoteUpdateData data) => instances.Contains(data.Note.User.Host);
|
||||
}
|
||||
|
||||
public class ForceMediaSensitivePolicyConfiguration : IPolicyConfiguration<ForceMediaSensitiveRewritePolicy>
|
||||
{
|
||||
private string[] _instances = [];
|
||||
public ForceMediaSensitiveRewritePolicy Apply() => new(Enabled, Priority, Instances);
|
||||
IPolicy IPolicyConfiguration. Apply() => Apply();
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
public int Priority { get; set; }
|
||||
|
||||
public string[] Instances
|
||||
{
|
||||
get => _instances;
|
||||
set => _instances = value.Select(p => p.ToLowerInvariant()).ToArray();
|
||||
}
|
||||
}
|
56
Iceshrimp.Backend/Core/Policies/StripMediaRewritePolicy.cs
Normal file
56
Iceshrimp.Backend/Core/Policies/StripMediaRewritePolicy.cs
Normal file
|
@ -0,0 +1,56 @@
|
|||
using Iceshrimp.Backend.Core.Database.Tables;
|
||||
using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
|
||||
using Iceshrimp.Backend.Core.Services;
|
||||
|
||||
namespace Iceshrimp.Backend.Core.Policies;
|
||||
|
||||
public class StripMediaRewritePolicy(
|
||||
bool enabled,
|
||||
int priority,
|
||||
string[] instances
|
||||
) : IRewritePolicy
|
||||
{
|
||||
public string Name => nameof(StripMediaRewritePolicy);
|
||||
public bool Enabled => enabled;
|
||||
public int Priority => priority;
|
||||
public IRewritePolicy.HookLocationEnum HookLocation => IRewritePolicy.HookLocationEnum.PreLogic;
|
||||
|
||||
public void Apply(NoteService.NoteCreationData data)
|
||||
{
|
||||
if (data.User.Host == null) return;
|
||||
if (IsApplicable(data.User.Host))
|
||||
data.Attachments = [];
|
||||
}
|
||||
|
||||
public void Apply(NoteService.NoteUpdateData data)
|
||||
{
|
||||
if (data.Note.User.Host == null) return;
|
||||
if (IsApplicable(data.Note.User.Host))
|
||||
data.Attachments = [];
|
||||
}
|
||||
|
||||
public void Apply(ASNote note, User actor)
|
||||
{
|
||||
if (actor.Host == null) return;
|
||||
if (IsApplicable(actor.Host))
|
||||
note.Attachments = [];
|
||||
}
|
||||
|
||||
private bool IsApplicable(string host) => instances.Contains(host);
|
||||
}
|
||||
|
||||
public class StripMediaRewritePolicyConfiguration : IPolicyConfiguration<StripMediaRewritePolicy>
|
||||
{
|
||||
private string[] _instances = [];
|
||||
public StripMediaRewritePolicy Apply() => new(Enabled, Priority, Instances);
|
||||
IPolicy IPolicyConfiguration. Apply() => Apply();
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
public int Priority { get; set; }
|
||||
|
||||
public string[] Instances
|
||||
{
|
||||
get => _instances;
|
||||
set => _instances = value.Select(p => p.ToLowerInvariant()).ToArray();
|
||||
}
|
||||
}
|
|
@ -849,6 +849,8 @@ public class NoteService(
|
|||
if (await fedCtrlSvc.ShouldBlockAsync(note.Id, actor.Host))
|
||||
throw GracefulException.UnprocessableEntity("Refusing to create note for user on blocked instance");
|
||||
|
||||
policySvc.CallRewriteHooks(note, actor, IRewritePolicy.HookLocationEnum.PreLogic);
|
||||
|
||||
var replyUri = note.InReplyTo?.Id;
|
||||
var reply = replyUri != null ? await ResolveNoteAsync(replyUri, user: user) : null;
|
||||
Poll? poll = null;
|
||||
|
@ -946,6 +948,8 @@ public class NoteService(
|
|||
.Select(p => p.Id)
|
||||
.ToList();
|
||||
|
||||
policySvc.CallRewriteHooks(note, actor, IRewritePolicy.HookLocationEnum.PostLogic);
|
||||
|
||||
return await CreateNoteAsync(new NoteCreationData
|
||||
{
|
||||
User = actor,
|
||||
|
|
|
@ -3,6 +3,8 @@ using System.Reflection;
|
|||
using System.Text.Json;
|
||||
using Iceshrimp.AssemblyUtils;
|
||||
using Iceshrimp.Backend.Core.Database;
|
||||
using Iceshrimp.Backend.Core.Database.Tables;
|
||||
using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
|
||||
using Iceshrimp.Backend.Core.Helpers;
|
||||
using Iceshrimp.Shared.Configuration;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
@ -84,6 +86,14 @@ public class PolicyService(IServiceScopeFactory scopeFactory)
|
|||
foreach (var hook in hooks) hook.Apply(data);
|
||||
}
|
||||
|
||||
public void CallRewriteHooks(ASNote note, User actor, IRewritePolicy.HookLocationEnum location)
|
||||
{
|
||||
var hooks = _rewritePolicies.Where(p => p.Enabled && p.HookLocation == location)
|
||||
.OrderByDescending(p => p.Priority);
|
||||
|
||||
foreach (var hook in hooks) hook.Apply(note, actor);
|
||||
}
|
||||
|
||||
public async Task<Type?> GetConfigurationType(string name)
|
||||
{
|
||||
await Initialize();
|
||||
|
@ -129,11 +139,13 @@ public interface IRewritePolicy : IPolicy
|
|||
|
||||
public void Apply(NoteService.NoteCreationData data);
|
||||
public void Apply(NoteService.NoteUpdateData data);
|
||||
public void Apply(ASNote note, User actor);
|
||||
}
|
||||
|
||||
public interface IRejectPolicy : IPolicy
|
||||
{
|
||||
public bool ShouldReject(NoteService.NoteCreationData data);
|
||||
//TODO: reject during note update & note process
|
||||
}
|
||||
|
||||
public interface IPolicyConfiguration
|
||||
|
|
Loading…
Add table
Reference in a new issue