diff --git a/MatrixInviteLogger/InviteLogger.cs b/MatrixInviteLogger/InviteLogger.cs
new file mode 100644
index 0000000..22a519b
--- /dev/null
+++ b/MatrixInviteLogger/InviteLogger.cs
@@ -0,0 +1,117 @@
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using ArcaneLibs;
+using ArcaneLibs.Extensions;
+using LibMatrix.EventTypes.Spec.State.RoomInfo;
+using LibMatrix.Helpers;
+using LibMatrix.Utilities.Bot.Services;
+using MatrixInviteLogger;
+
+public class InviteLogger(ILogger<InviteLogger> logger, InviteLoggerConfiguration config) : InviteHandlerHostedService.IInviteHandler
+{
+ public async Task HandleInviteAsync(InviteHandlerHostedService.InviteEventArgs invite)
+ {
+ logger.LogInformation("Received invite to room {}", invite.RoomId);
+ 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();
+ 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.SendInviteDataAsFile)
+ {
+ 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/MatrixInviteLogger/InviteLoggerConfiguration.cs b/MatrixInviteLogger/InviteLoggerConfiguration.cs
new file mode 100644
index 0000000..8fe1a29
--- /dev/null
+++ b/MatrixInviteLogger/InviteLoggerConfiguration.cs
@@ -0,0 +1,9 @@
+using Microsoft.Extensions.Configuration;
+
+namespace MatrixInviteLogger;
+
+public class InviteLoggerConfiguration {
+ public InviteLoggerConfiguration(IConfiguration config) => config.GetRequiredSection("InviteLogger").Bind(this);
+ public string LogRoom { get; set; }
+ public bool SendInviteDataAsFile { get; set; }
+}
\ No newline at end of file
diff --git a/MatrixInviteLogger/MatrixInviteLogger.csproj b/MatrixInviteLogger/MatrixInviteLogger.csproj
new file mode 100644
index 0000000..5a322ce
--- /dev/null
+++ b/MatrixInviteLogger/MatrixInviteLogger.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/MatrixInviteLogger/Program.cs b/MatrixInviteLogger/Program.cs
new file mode 100644
index 0000000..403f066
--- /dev/null
+++ b/MatrixInviteLogger/Program.cs
@@ -0,0 +1,16 @@
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using LibMatrix.Services;
+using LibMatrix.Utilities.Bot;
+using MatrixInviteLogger;
+
+var builder = Host.CreateApplicationBuilder(args);
+// builder.Services.AddHostedService<Worker>();
+
+builder.Services.AddSingleton<InviteLoggerConfiguration>();
+builder.Services.AddRoryLibMatrixServices()
+ .AddMatrixBot()
+ .WithInviteHandler<InviteLogger>();
+
+var host = builder.Build();
+host.Run();
\ No newline at end of file
diff --git a/MatrixInviteLogger/Properties/launchSettings.json b/MatrixInviteLogger/Properties/launchSettings.json
new file mode 100644
index 0000000..383a8e2
--- /dev/null
+++ b/MatrixInviteLogger/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/MatrixInviteLogger/appsettings.Development.json b/MatrixInviteLogger/appsettings.Development.json
new file mode 100644
index 0000000..ffdfb9c
--- /dev/null
+++ b/MatrixInviteLogger/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"
+ },
+ "InviteLogger": {
+ "LogRoom": "!GrLSwdAkdrvfMrRYKR:rory.gay",
+ "SendInviteDataAsFile": true // default: false
+ }
+}
diff --git a/MatrixInviteLogger/appsettings.json b/MatrixInviteLogger/appsettings.json
new file mode 100644
index 0000000..b2dcdb6
--- /dev/null
+++ b/MatrixInviteLogger/appsettings.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ }
+}
|