diff --git a/LibMatrix b/LibMatrix
-Subproject 44bfb17b1da1bd24140da7d9dcff56fa6fce45e
+Subproject c076141883ae7e02fcef33b8aed2bcbc0a53c16
diff --git a/MatrixAntiDmSpam/InviteHandler.cs b/MatrixAntiDmSpam/InviteHandler.cs
index c4831ee..2a5d17b 100644
--- a/MatrixAntiDmSpam/InviteHandler.cs
+++ b/MatrixAntiDmSpam/InviteHandler.cs
@@ -7,10 +7,11 @@ using LibMatrix.Utilities.Bot.Services;
namespace MatrixAntiDmSpam;
-public class InviteHandler(ILogger<InviteHandler> logger, AntiDmSpamConfiguration config) : InviteHandlerHostedService.IInviteHandler {
+public class InviteHandler(ILogger<InviteHandler> logger, AntiDmSpamConfiguration config, InviteStore inviteStore) : InviteHandlerHostedService.IInviteHandler {
public async Task HandleInviteAsync(InviteHandlerHostedService.InviteEventArgs invite) {
// logger.LogInformation("Received invite to room {}", invite.RoomId);
await LogInvite(invite);
+ await inviteStore.AddInviteAsync(invite);
}
private async Task LogInvite(InviteHandlerHostedService.InviteEventArgs invite) {
diff --git a/MatrixAntiDmSpam/InviteStore.cs b/MatrixAntiDmSpam/InviteStore.cs
index abfa49c..d6e8d49 100644
--- a/MatrixAntiDmSpam/InviteStore.cs
+++ b/MatrixAntiDmSpam/InviteStore.cs
@@ -1,14 +1,14 @@
-using LibMatrix;
-using System.Collections;
-using LibMatrix.EventTypes.Spec.State.Policy;
-using LibMatrix.Responses;
using LibMatrix.Utilities.Bot.Services;
namespace MatrixAntiDmSpam;
public class InviteStore {
- public void AddInvite(SyncResponse.RoomsDataStructure.InvitedRoomDataStructure invite) {
-
+
+
+ public async Task AddInviteAsync(InviteHandlerHostedService.InviteEventArgs invite) {
+ foreach (var handler in OnInviteReceived) {
+ await handler(invite);
+ }
}
public List<Func<InviteHandlerHostedService.InviteEventArgs, Task>> OnInviteReceived { get; set; } = [];
diff --git a/MatrixAntiDmSpam/PolicyExecutor.cs b/MatrixAntiDmSpam/PolicyExecutor.cs
index 2f93f11..a6ca404 100644
--- a/MatrixAntiDmSpam/PolicyExecutor.cs
+++ b/MatrixAntiDmSpam/PolicyExecutor.cs
@@ -1,28 +1,112 @@
+using ArcaneLibs.Extensions;
using LibMatrix.EventTypes.Spec.State.Policy;
+using LibMatrix.EventTypes.Spec.State.RoomInfo;
+using LibMatrix.Helpers;
using LibMatrix.Homeservers;
+using LibMatrix.RoomTypes;
using LibMatrix.Utilities.Bot.Services;
namespace MatrixAntiDmSpam;
-public class PolicyExecutor(ILogger<PolicyExecutor> logger, InviteStore inviteStore, PolicyStore policyStore, AuthenticatedHomeserverGeneric homeserver) : IHostedService {
+public class PolicyExecutor(
+ ILogger<PolicyExecutor> logger,
+ AntiDmSpamConfiguration config,
+ InviteStore inviteStore,
+ PolicyStore policyStore,
+ AuthenticatedHomeserverGeneric homeserver) : IHostedService {
+ private GenericRoom? _logRoom = string.IsNullOrWhiteSpace(config.LogRoom) ? null : homeserver.GetRoom(config.LogRoom);
+
public async Task StartAsync(CancellationToken cancellationToken) {
inviteStore.OnInviteReceived.Add(CheckPoliciesAgainstInvite);
policyStore.OnPolicyUpdated.Add(CheckPolicyAgainstInvites);
}
- public async Task StopAsync(CancellationToken cancellationToken) {
-
- }
-
- public async Task CheckPoliciesAgainstInvite(InviteHandlerHostedService.InviteEventArgs inviteEventArgs) {
- if(policyStore.RoomPolicies.Any(x=>x.Value.Entity == inviteEventArgs.RoomId))
- {
+ public async Task StopAsync(CancellationToken cancellationToken) { }
+
+ public async Task CheckPoliciesAgainstInvite(InviteHandlerHostedService.InviteEventArgs invite) {
+ string roomName = await GetRoomNameAsync(invite);
+
+ var roomPolicy = policyStore.RoomPolicies.Any(x => x.Value.EntityMatches(invite.RoomId))
+ ? policyStore.RoomPolicies.First(x => x.Value.EntityMatches(invite.RoomId)).Value
+ : null;
+
+ if (roomPolicy is not null) {
+ logger.LogWarning("Rejecting invite to {}, matching room policy {}", invite.RoomId, roomPolicy.ToJson(ignoreNull: true));
+
+ var message = new MessageBuilder()
+ .WithColoredBody("#FF0000", cb => cb.WithBody("Rejecting invite to ").WithMention(invite.RoomId, roomName).WithBody(", matching room policy.").WithNewline())
+ .WithCollapsibleSection("Policy JSON", cb => cb.WithCodeBlock(roomPolicy.ToJson(ignoreNull: true), "json"))
+ .Build();
+
+ if (_logRoom is not null)
+ await _logRoom.SendMessageEventAsync(message);
+ await homeserver.GetRoom(invite.RoomId).LeaveAsync();
+ }
+
+ var userPolicy = policyStore.UserPolicies.Any(x => x.Value.EntityMatches(invite.MemberEvent.Sender!))
+ ? policyStore.UserPolicies.First(x => x.Value.EntityMatches(invite.MemberEvent.Sender!)).Value
+ : null;
+
+ if (userPolicy is not null) {
+ logger.LogWarning("Rejecting invite to {}, matching user policy {}", invite.RoomId, userPolicy.ToJson(ignoreNull: true));
+
+ var message = new MessageBuilder()
+ .WithColoredBody("#FF0000", cb => cb.WithBody("Rejecting invite to ").WithMention(invite.RoomId, roomName).WithBody(", matching user policy.").WithNewline())
+ .WithCollapsibleSection("Policy JSON", cb => cb.WithCodeBlock(userPolicy.ToJson(ignoreNull: true), "json"))
+ .Build();
+
+ if (_logRoom is not null)
+ await _logRoom.SendMessageEventAsync(message);
+
+ await homeserver.GetRoom(invite.RoomId).LeaveAsync();
}
-
}
-
+
public async Task CheckPolicyAgainstInvites(PolicyRuleEventContent policy) {
-
+ Console.WriteLine("CheckPolicyAgainstInvites called!!!!");
+ }
+
+ private async Task<string> GetRoomNameAsync(InviteHandlerHostedService.InviteEventArgs invite) {
+ // try to get room name from invite state
+ var name = invite.InviteData.InviteState?.Events?
+ .FirstOrDefault(evt => evt is { Type: RoomNameEventContent.EventId, StateKey: "" })?
+ .ContentAs<RoomNameEventContent>()?.Name;
+
+ if (!string.IsNullOrWhiteSpace(name))
+ return name;
+
+ // try to get room alias
+ var alias = invite.InviteData.InviteState?.Events?
+ .FirstOrDefault(evt => evt is { Type: RoomCanonicalAliasEventContent.EventId, StateKey: "" })?
+ .ContentAs<RoomCanonicalAliasEventContent>()?.Alias;
+
+ if (!string.IsNullOrWhiteSpace(alias))
+ return alias;
+
+ // try get room name via public previews
+ try {
+#pragma warning disable CS0618 // Type or member is obsolete
+ name = await invite.Homeserver.GetRoom(invite.RoomId).GetNameOrFallbackAsync();
+#pragma warning restore CS0618 // Type or member is obsolete
+ if (name != invite.RoomId && !string.IsNullOrWhiteSpace(name))
+ return name;
+ }
+ catch {
+ //ignored
+ }
+
+ // fallback to room alias via public previews
+ try {
+ alias = (await invite.Homeserver.GetRoom(invite.RoomId).GetCanonicalAliasAsync())?.Alias;
+ if (!string.IsNullOrWhiteSpace(alias))
+ return alias;
+ }
+ catch {
+ //ignored
+ }
+
+ // fall back to room ID
+ return invite.RoomId;
}
}
\ No newline at end of file
diff --git a/MatrixAntiDmSpam/PolicyListFetcher.cs b/MatrixAntiDmSpam/PolicyListFetcher.cs
index a068f33..9a521b2 100644
--- a/MatrixAntiDmSpam/PolicyListFetcher.cs
+++ b/MatrixAntiDmSpam/PolicyListFetcher.cs
@@ -4,7 +4,8 @@ using LibMatrix.RoomTypes;
namespace MatrixAntiDmSpam;
-public class PolicyListFetcher(ILogger<PolicyListFetcher> logger, AntiDmSpamConfiguration config, AuthenticatedHomeserverGeneric homeserver, PolicyStore policyStore) : IHostedService {
+public class PolicyListFetcher(ILogger<PolicyListFetcher> logger, AntiDmSpamConfiguration config, AuthenticatedHomeserverGeneric homeserver, PolicyStore policyStore)
+ : IHostedService {
public async Task StartAsync(CancellationToken cancellationToken) {
logger.LogInformation("Starting policy list fetcher");
await EnsurePolicyListsJoined();
@@ -29,11 +30,14 @@ public class PolicyListFetcher(ILogger<PolicyListFetcher> logger, AntiDmSpamConf
private async Task LoadPolicyLists() {
foreach (var room in config.PolicyLists) {
var sw = Stopwatch.StartNew();
- await LoadPolicyList(homeserver.GetRoom(room.RoomId).AsPolicyRoom());
- logger.LogInformation("Loaded policy list {} in {}", room.RoomId, sw.Elapsed);
+ var count = await LoadPolicyList(homeserver.GetRoom(room.RoomId).AsPolicyRoom());
+ logger.LogInformation("Loaded policy list {} ({}) in {}, with {} policies", room.Name, room.RoomId, sw.Elapsed, count);
}
}
- private async Task LoadPolicyList(PolicyRoom room) {
- policyStore.AddPolicies(room.GetPoliciesAsync().ToBlockingEnumerable());
+
+ private async Task<int> LoadPolicyList(PolicyRoom room) {
+ var policies = room.GetPoliciesAsync().ToBlockingEnumerable().ToList();
+ policyStore.AddPolicies(policies);
+ return policies.Count();
}
}
\ No newline at end of file
diff --git a/MatrixAntiDmSpam/PolicyStore.cs b/MatrixAntiDmSpam/PolicyStore.cs
index d16f8df..22f9225 100644
--- a/MatrixAntiDmSpam/PolicyStore.cs
+++ b/MatrixAntiDmSpam/PolicyStore.cs
@@ -10,9 +10,9 @@ public class PolicyStore {
public Dictionary<string, RoomPolicyRuleEventContent> RoomPolicies { get; } = [];
public List<Func<PolicyRuleEventContent, Task>> OnPolicyUpdated { get; } = [];
- public void AddPolicies(IEnumerable<StateEventResponse> events) => events.ToList().ForEach(AddPolicy);
+ public async Task AddPolicies(IEnumerable<StateEventResponse> events) => events.ToList().Select(AddPolicy).ToList();
- public void AddPolicy(StateEventResponse evt) {
+ public async Task AddPolicy(StateEventResponse evt) {
var eventKey = $"{evt.RoomId}:{evt.Type}:{evt.StateKey}";
// UserPolicies.ToList().ForEach(x => Console.WriteLine(x.Key));
switch (evt.TypedContent) {
@@ -26,5 +26,9 @@ public class PolicyStore {
RoomPolicies[eventKey] = roomPolicy;
break;
}
+
+ foreach (var callback in OnPolicyUpdated) {
+ await callback((evt.TypedContent as PolicyRuleEventContent)!);
+ }
}
}
\ No newline at end of file
diff --git a/MatrixAntiDmSpam/Program.cs b/MatrixAntiDmSpam/Program.cs
index f813eab..e81cd58 100644
--- a/MatrixAntiDmSpam/Program.cs
+++ b/MatrixAntiDmSpam/Program.cs
@@ -11,6 +11,7 @@ builder.Services.AddRoryLibMatrixServices()
.WithInviteHandler<InviteHandler>();
builder.Services.AddHostedService<PolicyListFetcher>();
+builder.Services.AddHostedService<PolicyExecutor>();
builder.Services.AddSingleton<InviteStore>();
builder.Services.AddSingleton<PolicyStore>();
diff --git a/MatrixAntiDmSpam/appsettings.Development.json b/MatrixAntiDmSpam/appsettings.Development.json
index 958cc7a..fd582b4 100644
--- a/MatrixAntiDmSpam/appsettings.Development.json
+++ b/MatrixAntiDmSpam/appsettings.Development.json
@@ -1,4 +1,5 @@
{
+ // Don't touch this unless you know what you're doing:
"Logging": {
"LogLevel": {
"Default": "Information",
@@ -6,18 +7,41 @@
}
},
"LibMatrixBot": {
+ // Homeserver to connect to.
+ // Note: Homeserver resolution is applied here, but a direct base URL can be used.
"Homeserver": "rory.gay",
- "AccessTokenPath": "/home/Rory/matrix_access_token"
+
+ // Absolute path to the file containing the access token
+ "AccessTokenPath": "/home/Rory/matrix_access_token",
+ "InviteHandler": {
+ "SyncConfiguration": {
+ // How long to wait until the sync request times out
+ // "Timeout": 300000,
+
+ // Minimum sync interval, useful if you want to have less traffic than a normal client.
+ "MinimumSyncTime": "00:00:10.000",
+
+ // What presence value to set
+ // Defaults to "online" if null or not set
+ // "Presence": "online",
+
+ // Filter to apply to the sync request. Useful if you want custom data to be sent.
+ // "Filter": { },
+
+ // Whether to initial sync on startup - very useful in development, or just to be sure.
+ "InitialSyncOnStartup": true
+ }
+ }
},
"AntiDmSpam": {
"LogRoom": "!GrLSwdAkdrvfMrRYKR:rory.gay",
"LogInviteDataAsFile": true,
"PolicyLists": [
- {
- "Name": "Community Moderation Effort",
- "RoomId": "!fTjMjIzNKEsFlUIiru:neko.dev",
- "Vias": [ "rory.gay" ]
- }
+ {
+ "Name": "Community Moderation Effort",
+ "RoomId": "!fTjMjIzNKEsFlUIiru:neko.dev",
+ "Vias": [ "rory.gay" ]
+ }
]
}
-}
+}
\ No newline at end of file
|