From 41b14d6621e2579edf55ccf83ca563cb31d09a2d Mon Sep 17 00:00:00 2001 From: Rory& Date: Wed, 26 Mar 2025 11:43:17 +0100 Subject: Simplify invite store, use multiple threads for evaluating policies to scale beyond unreasonable policy counts --- MatrixAntiDmSpam/InviteHandler.cs | 140 ++++++++++++++++---------------------- 1 file changed, 59 insertions(+), 81 deletions(-) (limited to 'MatrixAntiDmSpam/InviteHandler.cs') diff --git a/MatrixAntiDmSpam/InviteHandler.cs b/MatrixAntiDmSpam/InviteHandler.cs index 2a5d17b..26a88c7 100644 --- a/MatrixAntiDmSpam/InviteHandler.cs +++ b/MatrixAntiDmSpam/InviteHandler.cs @@ -1,113 +1,91 @@ using System.Text.Json; using ArcaneLibs; using ArcaneLibs.Extensions; -using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Helpers; -using LibMatrix.Utilities.Bot.Services; +using LibMatrix.RoomTypes; +using LibMatrix.Utilities.Bot.Interfaces; namespace MatrixAntiDmSpam; -public class InviteHandler(ILogger 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); - } +public class RoomInviteHandler(ILogger logger, AntiDmSpamConfiguration config) : IRoomInviteHandler { + public List Invites { get; } = []; - 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); + public List> OnInviteReceived { get; set; } = []; - logger.LogInformation("Inviter: {}, Room: {}", inviterName, roomName); + private GenericRoom? LogRoom { get; set; } - var message = new MessageBuilder() - .WithBody("Received invite to ").WithMention(invite.RoomId, roomName).WithBody(" from ").WithMention(invite.MemberEvent.Sender!, inviterName) - .Build(); + public async Task HandleInviteAsync(RoomInviteContext invite) { + if (!string.IsNullOrWhiteSpace(config.LogRoom)) + LogRoom = invite.Homeserver.GetRoom(config.LogRoom); - // 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!; + Invites.Add(invite); + await LogInvite(invite); + foreach (var handler in OnInviteReceived) { + try { + await handler.Invoke(invite); + } + catch (Exception e) { + if (!string.IsNullOrWhiteSpace(config.LogRoom)) { + var logRoom = invite.Homeserver.GetRoom(config.LogRoom); + await logRoom.SendMessageEventAsync( + new MessageBuilder() + .WithBody($"Failed to execute invite handler {handler}...").WithNewline() + .WithCollapsibleSection("Stack trace", msb => msb.WithCodeBlock(e.ToString(), "cs")) + .Build() + ); + } + } + } + } + private async Task LogInvite(RoomInviteContext invite) { + logger.LogInformation("Received invite to {} from {}", invite.RoomId, invite.MemberEvent.Sender); + + if (LogRoom is null) return; 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 - message.AdditionalData!["gay.rory.invite_logger.invite_data_uri"] = JsonDocument.Parse($"\"{inviteDataFileUri}\"").RootElement; + var inviterNameTask = invite.TryGetInviterNameAsync(); + var roomNameTask = invite.TryGetRoomNameAsync(); + var inviteDataFileUriTask = invite.Homeserver.UploadFile(invite.RoomId + ".json", inviteData, "application/json"); + logger.LogInformation("Uploaded invite data ({}) to {}", Util.BytesToString(inviteData.Length), await inviteDataFileUriTask); + + await Task.WhenAll(inviterNameTask, roomNameTask, inviteDataFileUriTask); - await logRoom.SendMessageEventAsync(message); + var message = new MessageBuilder() + .WithBody("Received invite to ").WithMention(invite.RoomId, await roomNameTask).WithBody(" from ").WithMention(invite.MemberEvent.Sender!, await inviterNameTask) + .Build(); + + message.AdditionalData!["gay.rory.invite_logger.invite_data_uri"] = JsonDocument.Parse($"\"{await inviteDataFileUriTask}\"").RootElement; + + await LogRoom.SendMessageEventAsync(message); if (config.LogInviteDataAsFile) { - await logRoom.SendMessageEventAsync(new() { + await LogRoom.SendMessageEventAsync(new() { MessageType = "m.file", Body = invite.RoomId + ".json", FileName = invite.RoomId + ".json", - Url = inviteDataFileUri, + Url = await inviteDataFileUriTask, FileInfo = new() { Size = inviteData.Length, MimeType = "application/json" } }); } } - private async Task GetInviterNameAsync(InviteHandlerHostedService.InviteEventArgs invite) { - var name = invite.InviteData.InviteState?.Events? - .FirstOrDefault(evt => evt is { Type: RoomMemberEventContent.EventId } && evt.StateKey == invite.MemberEvent.Sender)? - .ContentAs()?.DisplayName; - - if (!string.IsNullOrWhiteSpace(name)) - return name; - - try { - await invite.Homeserver.GetProfileAsync(invite.MemberEvent.Sender!); - } - catch { - //ignored - } - - return invite.MemberEvent.Sender!; - } - - private async Task 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()?.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()?.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 - } + public async Task RejectInvite(RoomInviteContext invite, MessageBuilder reason) { + if (LogRoom is not null) + _ = LogRoom.SendMessageEventAsync(reason.Build()); - // fallback to room alias via public previews try { - alias = (await invite.Homeserver.GetRoom(invite.RoomId).GetCanonicalAliasAsync())?.Alias; - if (!string.IsNullOrWhiteSpace(alias)) - return alias; + await invite.Homeserver.GetRoom(invite.RoomId).LeaveAsync(); } - catch { - //ignored + catch (Exception e) { + if (LogRoom is not null) + await LogRoom.SendMessageEventAsync( + new MessageBuilder().WithColoredBody("#FF8800", $"Failed to leave {invite.RoomId}:").WithNewline() + .WithCodeBlock(e.ToString(), "cs") + .Build() + ); } - // fall back to room ID - return invite.RoomId; + Invites.Remove(invite); } } \ No newline at end of file -- cgit 1.5.1