about summary refs log tree commit diff
path: root/MatrixAntiDmSpam/PolicyExecutor.cs
blob: a6ca404e15d557b5d04f0a596d09b5806ec78d00 (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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
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,
    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 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;
    }
}