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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using ArcaneLibs.Attributes;
using ArcaneLibs.Extensions;
namespace LibMatrix.EventTypes.Spec.State.Policy;
//spec
[MatrixEvent(EventName = EventId)] //spec
[MatrixEvent(EventName = "m.room.rule.server", Legacy = true)] //???
[MatrixEvent(EventName = "org.matrix.mjolnir.rule.server", Legacy = true)] //legacy
[FriendlyName(Name = "Server policy", NamePlural = "Server policies")]
public class ServerPolicyRuleEventContent : PolicyRuleEventContent {
public const string EventId = "m.policy.rule.server";
}
[MatrixEvent(EventName = EventId)] //spec
[MatrixEvent(EventName = "m.room.rule.user", Legacy = true)] //???
[MatrixEvent(EventName = "org.matrix.mjolnir.rule.user", Legacy = true)] //legacy
[FriendlyName(Name = "User policy", NamePlural = "User policies")]
public class UserPolicyRuleEventContent : PolicyRuleEventContent {
public const string EventId = "m.policy.rule.user";
}
[MatrixEvent(EventName = EventId)] //spec
[MatrixEvent(EventName = "m.room.rule.room", Legacy = true)] //???
[MatrixEvent(EventName = "org.matrix.mjolnir.rule.room", Legacy = true)] //legacy
[FriendlyName(Name = "Room policy", NamePlural = "Room policies")]
public class RoomPolicyRuleEventContent : PolicyRuleEventContent {
public const string EventId = "m.policy.rule.room";
}
[DebuggerDisplay("""{GetType().Name.Replace("PolicyRuleEventContent", ""),nq} policy matching {Entity}, Reason: {Reason}""")]
public abstract class PolicyRuleEventContent : EventContent {
// public PolicyRuleEventContent() => Console.WriteLine($"init policy {GetType().Name}");
/// <summary>
/// Entity this ban applies to, can use * and ? as globs.
/// Policy is invalid if entity is null
/// </summary>
[JsonPropertyName("entity")]
[FriendlyName(Name = "Entity")]
public string? Entity { get; set; }
// private bool init;
/// <summary>
/// Reason this user is banned
/// </summary>
[JsonPropertyName("reason")]
[FriendlyName(Name = "Reason")]
public string? Reason { get; set; }
/// <summary>
/// Suggested action to take
/// </summary>
[JsonPropertyName("recommendation")]
[FriendlyName(Name = "Recommendation")]
public string? Recommendation { get; set; }
/// <summary>
/// Expiry time in milliseconds since the unix epoch, or null if the ban has no expiry.
/// </summary>
[JsonPropertyName("support.feline.policy.expiry.rev.2")] //stable prefix: expiry, msc pending
[TableHide]
public long? Expiry { get; set; }
//utils
/// <summary>
/// Readable expiry time, provided for easy interaction
/// </summary>
[JsonPropertyName("gay.rory.matrix_room_utils.readable_expiry_time_utc")]
[FriendlyName(Name = "Expires at")]
[TableHide]
public DateTime? ExpiryDateTime {
get => Expiry == null ? null : DateTimeOffset.FromUnixTimeMilliseconds(Expiry.Value).DateTime;
set {
if (value is not null)
Expiry = ((DateTimeOffset)value).ToUnixTimeMilliseconds();
}
}
[JsonPropertyName("org.matrix.msc4205.hashes")]
[TableHide]
public PolicyHash? Hashes { get; set; }
public string GetDraupnir2StateKey() => Convert.ToBase64String(SHA256.HashData($"{Entity}{Recommendation}".AsBytes().ToArray()));
public Regex? GetEntityRegex() => Entity is null ? null : new(Entity.Replace(".", "\\.").Replace("*", ".*").Replace("?", "."));
public bool IsGlobRule() =>
!string.IsNullOrWhiteSpace(Entity)
&& (Entity.Contains('*') || Entity.Contains('?'));
public bool EntityMatches(string entity) {
if (string.IsNullOrWhiteSpace(entity)) return false;
if (!string.IsNullOrWhiteSpace(Entity)) {
// Check if entity is equal regardless of glob check
var match = Entity == entity
|| (IsGlobRule() && GetEntityRegex()!.IsMatch(entity));
if (match) return match;
}
if (Hashes is not null) {
if (!string.IsNullOrWhiteSpace(Hashes.Sha256)) {
var hash = SHA256.HashData(entity.AsBytes().ToArray());
var match = Convert.ToBase64String(hash) == Hashes.Sha256;
if (match) return match;
}
}
return false;
}
public string? GetNormalizedRecommendation() {
if (Recommendation is "m.ban" or "org.matrix.mjolnir.ban")
return PolicyRecommendationTypes.Ban;
if (Recommendation is "m.takedown" or "org.matrix.msc4204.takedown")
return "m.takedown";
return Recommendation;
}
}
public static class PolicyRecommendationTypes {
/// <summary>
/// Ban this user
/// </summary>
public static string Ban = "m.ban";
/// <summary>
/// Mute this user
/// </summary>
public static string Mute = "support.feline.policy.recommendation_mute"; //stable prefix: m.mute, msc pending
public static string Takedown = "m.takedown"; //unstable prefix: org.matrix.msc4204.takedown
}
public class PolicyHash {
[JsonPropertyName("sha256")]
public string? Sha256 { get; set; }
}
// public class PolicySchemaDefinition {
// public required string Name { get; set; }
// public required bool Optional { get; set; }
//
// }
|