about summary refs log tree commit diff
path: root/MatrixContentFilter/Handlers/Filters/PendingInviteLimiter.cs
blob: b1b1e8b64a7cd0252858f4b2751f78a1b390e7e3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
using System.Collections.Concurrent;
using ArcaneLibs.Extensions;
using LibMatrix;
using LibMatrix.EventTypes.Spec.State.RoomInfo;
using LibMatrix.Helpers;
using LibMatrix.Homeservers;
using LibMatrix.Responses;
using LibMatrix.RoomTypes;
using MatrixContentFilter.Abstractions;
using MatrixContentFilter.EventTypes;
using MatrixContentFilter.Services;
using MatrixContentFilter.Services.AsyncActionQueues;

namespace MatrixContentFilter.Handlers.Filters;

public class PendingInviteLimiter(
    ConfigurationService cfgService,
    AuthenticatedHomeserverGeneric hs,
    AsyncMessageQueue msgQueue,
    InfoCacheService infoCache,
    AbstractAsyncActionQueue actionQueue,
    MatrixContentFilterMetrics metrics)
    : IContentFilter {
    public override Task ProcessSyncAsync(SyncResponse syncResponse) {
        if (syncResponse.Rooms?.Join is null) return Task.CompletedTask;
        var tasks = syncResponse.Rooms.Join.Select(ProcessRoomAsync);
        return Task.WhenAll(tasks);
    }

    private Task ProcessRoomAsync(KeyValuePair<string, SyncResponse.RoomsDataStructure.JoinedRoomDataStructure> syncRoom) {
        var (roomId, roomData) = syncRoom;
        if (roomId == cfgService.LogRoom.RoomId || roomId == cfgService.ControlRoom.RoomId) return Task.CompletedTask;
        if (roomData.Timeline?.Events is null) return Task.CompletedTask;
        var config = cfgService.RoomConfigurationOverrides.GetValueOrDefault(roomId)?.InviteLimiter;

        var room = hs.GetRoom(roomId);

        var tasks = roomData.Timeline.Events.Select(msg => ProcessEventAsync(room, msg, config)).ToList();
        return Task.WhenAll(tasks);
    }

    public override Task ProcessEventListAsync(List<StateEventResponse> events) {
        var tasks = events.GroupBy(x => x.RoomId).Select(async x => {
            var room = hs.GetRoom(x.Key);
            var config = cfgService.RoomConfigurationOverrides.GetValueOrDefault(x.Key)?.InviteLimiter;
            var tasks = x.Select(msg => ProcessEventAsync(room, msg, config)).ToList();
            await Task.WhenAll(tasks);
        }).ToList();
        
        return Task.WhenAll(tasks);
    }

    // key format: roomid:sender
    private ConcurrentDictionary<string, int> HeuristicInviteCount = new();
    private async Task ProcessEventAsync(GenericRoom room, StateEventResponse evt, FilterConfiguration.BasicLimiterConfiguration? roomConfiguration) {
        if (evt.Type != "m.room.member") return;
        var content = evt.TypedContent as RoomMemberEventContent;
        if (content?.Membership != "invite") return;
        metrics.Increment("pending_invite_limiter_processed_event_count");

        var key = $"{evt.RoomId}:{evt.Sender}";
        HeuristicInviteCount.AddOrUpdate(key, 1, (_, count) => count + 1);
        if (HeuristicInviteCount[key] > 5) {
            await actionQueue.EqueueActionAsync(evt.EventId, async () => {
                var displayName = await infoCache.GetDisplayNameAsync(room.RoomId, evt.Sender!);
                var roomName = await infoCache.GetRoomNameAsync(room.RoomId);
                
                msgQueue.EnqueueMessageAsync(cfgService.LogRoom, new MessageBuilder("m.notice")
                    .WithBody("Pending invite limiter heuristic tripped for ").WithMention(evt.Sender!).WithBody(" in ").WithMention(evt.RoomId, roomName).WithBody($" ({HeuristicInviteCount[key]})!").WithNewline()
                    .WithBody("Updating heuristics with real counts...")
                    .Build());

                var invitedMembersByInviter = (await room.GetMembersListAsync(joinedOnly: false))
                    .Where(x => x.ContentAs<RoomMemberEventContent>()!.Membership == "invite")
                    .GroupBy(x=>x.Sender!);

                foreach (var sender in invitedMembersByInviter) {
                    HeuristicInviteCount.AddOrUpdate($"{room.RoomId}:{sender.Key}", sender.Count(), (_, count) => count + sender.Count());
                    msgQueue.EnqueueMessageAsync(cfgService.LogRoom, new MessageBuilder("m.notice")
                        .WithBody("Updated heuristic count for ").WithMention(sender.Key).WithBody(" in ").WithMention(room.RoomId, roomName).WithBody($" ({HeuristicInviteCount[key]})!").WithNewline()
                        .Build());
                }

                // msgQueue.EnqueueMessageAsync(cfgService.LogRoom, new MessageBuilder("m.notice")
                    // .WithBody("Invite from ").WithMention(evt.Sender, displayName).WithBody(" in ").WithMention(room.RoomId, roomName).WithBody(" was rejected!").WithNewline()
                    // .WithCollapsibleSection("Message data", msb => msb.WithCodeBlock(content.ToJson(ignoreNull: true), "json"))
                    // .Build());
            });
        }
        ActionCount++;
    }
}