about summary refs log tree commit diff
path: root/MatrixAntiDmSpam
diff options
context:
space:
mode:
Diffstat (limited to 'MatrixAntiDmSpam')
-rw-r--r--MatrixAntiDmSpam/AntiDmSpamConfiguration.cs14
-rw-r--r--MatrixAntiDmSpam/InviteHandler.cs110
-rw-r--r--MatrixAntiDmSpam/MatrixAntiDmSpam.csproj17
-rw-r--r--MatrixAntiDmSpam/PolicyListFetcher.cs27
-rw-r--r--MatrixAntiDmSpam/Program.cs16
-rw-r--r--MatrixAntiDmSpam/Properties/launchSettings.json12
-rw-r--r--MatrixAntiDmSpam/appsettings.Development.json16
-rw-r--r--MatrixAntiDmSpam/appsettings.json8
8 files changed, 220 insertions, 0 deletions
diff --git a/MatrixAntiDmSpam/AntiDmSpamConfiguration.cs b/MatrixAntiDmSpam/AntiDmSpamConfiguration.cs
new file mode 100644

index 0000000..0e10a91 --- /dev/null +++ b/MatrixAntiDmSpam/AntiDmSpamConfiguration.cs
@@ -0,0 +1,14 @@ +namespace MatrixAntiDmSpam; + +public class AntiDmSpamConfiguration { + public AntiDmSpamConfiguration(IConfiguration config) => config.GetRequiredSection("AntiDmSpam").Bind(this); + public string? LogRoom { get; set; } + public bool LogInviteDataAsFile { get; set; } + + public List<PolicyRoomReference> PolicyLists { get; set; } + + public class PolicyRoomReference { + public string RoomId { get; set; } + public List<string> Vias { get; set; } + } +} \ No newline at end of file diff --git a/MatrixAntiDmSpam/InviteHandler.cs b/MatrixAntiDmSpam/InviteHandler.cs new file mode 100644
index 0000000..cfa04dc --- /dev/null +++ b/MatrixAntiDmSpam/InviteHandler.cs
@@ -0,0 +1,110 @@ +using System.Text.Json; +using ArcaneLibs; +using ArcaneLibs.Extensions; +using LibMatrix.EventTypes.Spec.State.RoomInfo; +using LibMatrix.Helpers; +using LibMatrix.Utilities.Bot.Services; + +namespace MatrixAntiDmSpam; + +public class InviteHandler(ILogger<InviteHandler> logger, AntiDmSpamConfiguration config) : InviteHandlerHostedService.IInviteHandler { + public async Task HandleInviteAsync(InviteHandlerHostedService.InviteEventArgs invite) { + logger.LogInformation("Received invite to room {}", invite.RoomId); + await LogInvite(invite); + } + + private async Task LogInvite(InviteHandlerHostedService.InviteEventArgs invite) { + if (string.IsNullOrWhiteSpace(config.LogRoom)) return; + var logRoom = invite.Homeserver.GetRoom(config.LogRoom); + var inviterName = await GetInviterNameAsync(invite); + string roomName = await GetRoomNameAsync(invite); + + logger.LogInformation("Inviter: {}, Room: {}", inviterName, roomName); + + var message = new MessageBuilder() + .WithBody("Received invite to ").WithMention(invite.RoomId, roomName).WithBody(" from ").WithMention(invite.MemberEvent.Sender!, inviterName) + .Build(); + + // TODO: can we filter this somehow to stay within event size limits? + // var serialisedInviteData = JsonNode.Parse(invite.InviteData.ToJson(ignoreNull: true)); + // message.AdditionalData!["gay.rory.invite_logger.invite_data"] = serialisedInviteData!; + + var inviteData = invite.InviteData.ToJsonUtf8Bytes(ignoreNull: true); + var inviteDataFileUri = await invite.Homeserver.UploadFile(invite.RoomId + ".json", inviteData, "application/json"); + logger.LogInformation("Uploaded invite data ({}) to {}", Util.BytesToString(inviteData.Length), inviteDataFileUri); + + // Dictionary<string, JsonElement> + message.AdditionalData!["gay.rory.invite_logger.invite_data_uri"] = JsonDocument.Parse($"\"{inviteDataFileUri}\"").RootElement; + + await logRoom.SendMessageEventAsync(message); + + if (config.LogInviteDataAsFile) { + await logRoom.SendMessageEventAsync(new() { + MessageType = "m.file", + Body = invite.RoomId + ".json", + FileName = invite.RoomId + ".json", + Url = inviteDataFileUri, + FileInfo = new() { Size = inviteData.Length, MimeType = "application/json" } + }); + } + } + + private async Task<string> GetInviterNameAsync(InviteHandlerHostedService.InviteEventArgs invite) { + var name = invite.InviteData.InviteState?.Events? + .FirstOrDefault(evt => evt is { Type: RoomMemberEventContent.EventId } && evt.StateKey == invite.MemberEvent.Sender)? + .ContentAs<RoomMemberEventContent>()?.DisplayName; + + if (!string.IsNullOrWhiteSpace(name)) + return name; + + try { + await invite.Homeserver.GetProfileAsync(invite.MemberEvent.Sender!); + } + catch { + //ignored + } + + return invite.MemberEvent.Sender!; + } + + private async Task<string> GetRoomNameAsync(InviteHandlerHostedService.InviteEventArgs invite) { + // try 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 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 { + name = await invite.Homeserver.GetRoom(invite.RoomId).GetNameOrFallbackAsync(); + 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/MatrixAntiDmSpam.csproj b/MatrixAntiDmSpam/MatrixAntiDmSpam.csproj new file mode 100644
index 0000000..5a322ce --- /dev/null +++ b/MatrixAntiDmSpam/MatrixAntiDmSpam.csproj
@@ -0,0 +1,17 @@ +<Project Sdk="Microsoft.NET.Sdk.Worker"> + + <PropertyGroup> + <TargetFramework>net9.0</TargetFramework> + <Nullable>enable</Nullable> + <ImplicitUsings>enable</ImplicitUsings> + <UserSecretsId>dotnet-MatrixInviteLogger-87d8c346-8c07-42f9-8bfb-f2a714bbd663</UserSecretsId> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.2"/> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\LibMatrix\Utilities\LibMatrix.Utilities.Bot\LibMatrix.Utilities.Bot.csproj" /> + </ItemGroup> +</Project> diff --git a/MatrixAntiDmSpam/PolicyListFetcher.cs b/MatrixAntiDmSpam/PolicyListFetcher.cs new file mode 100644
index 0000000..0119527 --- /dev/null +++ b/MatrixAntiDmSpam/PolicyListFetcher.cs
@@ -0,0 +1,27 @@ +using LibMatrix.Homeservers; + +namespace MatrixAntiDmSpam; + +public class PolicyListFetcher(ILogger<PolicyListFetcher> logger, AntiDmSpamConfiguration config, AuthenticatedHomeserverGeneric homeserver) : IHostedService { + + + public async Task StartAsync(CancellationToken cancellationToken) { + logger.LogInformation("Starting policy list fetcher"); + await EnsurePolicyListsJoined(); + } + + public async Task StopAsync(CancellationToken cancellationToken) { + logger.LogInformation("Stopping policy list fetcher"); + } + + private async Task EnsurePolicyListsJoined() { + var joinedRooms = await homeserver.GetJoinedRooms(); + var expectedPolicyRooms = config.PolicyLists; + var missingRooms = expectedPolicyRooms.Where(room => !joinedRooms.Any(r => r.RoomId == room.RoomId)).ToList(); + + foreach (var room in missingRooms) { + logger.LogInformation("Joining policy list room {}", room.RoomId); + await homeserver.GetRoom(room.RoomId).JoinAsync(room.Vias); + } + } +} \ No newline at end of file diff --git a/MatrixAntiDmSpam/Program.cs b/MatrixAntiDmSpam/Program.cs new file mode 100644
index 0000000..cbf1dc9 --- /dev/null +++ b/MatrixAntiDmSpam/Program.cs
@@ -0,0 +1,16 @@ +using System.ComponentModel; +using LibMatrix.Services; +using LibMatrix.Utilities.Bot; +using MatrixAntiDmSpam; + +var builder = Host.CreateApplicationBuilder(args); + +builder.Services.AddSingleton<AntiDmSpamConfiguration>(); +builder.Services.AddRoryLibMatrixServices() + .AddMatrixBot() + .WithInviteHandler<InviteHandler>(); + +builder.Services.AddHostedService<PolicyListFetcher>(); + +var host = builder.Build(); +host.Run(); \ No newline at end of file diff --git a/MatrixAntiDmSpam/Properties/launchSettings.json b/MatrixAntiDmSpam/Properties/launchSettings.json new file mode 100644
index 0000000..383a8e2 --- /dev/null +++ b/MatrixAntiDmSpam/Properties/launchSettings.json
@@ -0,0 +1,12 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "MatrixInviteLogger": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} diff --git a/MatrixAntiDmSpam/appsettings.Development.json b/MatrixAntiDmSpam/appsettings.Development.json new file mode 100644
index 0000000..45717aa --- /dev/null +++ b/MatrixAntiDmSpam/appsettings.Development.json
@@ -0,0 +1,16 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "LibMatrixBot": { + "Homeserver": "rory.gay", + "AccessTokenPath": "/home/Rory/matrix_access_token" + }, + "AntiDmSpam": { + "LogRoom": "!GrLSwdAkdrvfMrRYKR:rory.gay", + "LogInviteDataAsFile": true + } +} diff --git a/MatrixAntiDmSpam/appsettings.json b/MatrixAntiDmSpam/appsettings.json new file mode 100644
index 0000000..b2dcdb6 --- /dev/null +++ b/MatrixAntiDmSpam/appsettings.json
@@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } +}