diff --git a/LibMatrix.EventTypes/Common/MjolnirShortcodeEventContent.cs b/LibMatrix.EventTypes/Common/MjolnirShortcodeEventContent.cs
index a31cbbb..a1ebd79 100644
--- a/LibMatrix.EventTypes/Common/MjolnirShortcodeEventContent.cs
+++ b/LibMatrix.EventTypes/Common/MjolnirShortcodeEventContent.cs
@@ -3,7 +3,7 @@ using System.Text.Json.Serialization;
namespace LibMatrix.EventTypes.Common;
[MatrixEvent(EventName = EventId)]
-public class MjolnirShortcodeEventContent : TimelineEventContent {
+public class MjolnirShortcodeEventContent : EventContent {
public const string EventId = "org.matrix.mjolnir.shortcode";
[JsonPropertyName("shortcode")]
diff --git a/LibMatrix.EventTypes/Common/Msc2545EmoteRoomsAccountDataEventContent.cs b/LibMatrix.EventTypes/Common/Msc2545EmoteRoomsAccountDataEventContent.cs
new file mode 100644
index 0000000..4fd5c29
--- /dev/null
+++ b/LibMatrix.EventTypes/Common/Msc2545EmoteRoomsAccountDataEventContent.cs
@@ -0,0 +1,31 @@
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using System.Text.Json.Serialization;
+
+namespace LibMatrix.EventTypes.Spec;
+
+[MatrixEvent(EventName = EventId)]
+public class Msc2545EmoteRoomsAccountDataEventContent : EventContent {
+ public const string EventId = "im.ponies.emote_rooms";
+
+ [JsonPropertyName("rooms")]
+ public Dictionary<string, Dictionary<string, EnabledEmotePackEntry>> Rooms { get; set; } = new();
+
+ // Dummy type to provide easy access to the by-spec empty content
+ public class EnabledEmotePackEntry {
+ [JsonExtensionData]
+ public Dictionary<string, object>? AdditionalData { get; set; } = [];
+
+ public T? GetAdditionalData<T>(string key) where T : class {
+ if (AdditionalData == null || !AdditionalData.TryGetValue(key, out var value))
+ return null;
+
+ if (value is T tValue)
+ return tValue;
+ if (value is JsonElement jsonElement)
+ return jsonElement.Deserialize<T>();
+
+ throw new InvalidCastException($"Value for key '{key}' ({value.GetType()}) cannot be cast to type '{typeof(T)}'. Cannot continue.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/LibMatrix.EventTypes/EventContent.cs b/LibMatrix.EventTypes/EventContent.cs
index 07f56e2..d612e44 100644
--- a/LibMatrix.EventTypes/EventContent.cs
+++ b/LibMatrix.EventTypes/EventContent.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;
@@ -5,6 +6,7 @@ using System.Text.Json.Serialization;
namespace LibMatrix.EventTypes;
+[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global", Justification = "These get instantiated via reflection")]
public abstract class EventContent {
[JsonExtensionData]
public Dictionary<string, object>? AdditionalData { get; set; } = [];
diff --git a/LibMatrix.EventTypes/LibMatrix.EventTypes.csproj b/LibMatrix.EventTypes/LibMatrix.EventTypes.csproj
index 0924aba..31dfe16 100644
--- a/LibMatrix.EventTypes/LibMatrix.EventTypes.csproj
+++ b/LibMatrix.EventTypes/LibMatrix.EventTypes.csproj
@@ -1,14 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net9.0</TargetFramework>
+ <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="ArcaneLibs" Version="1.0.0-preview.20250419-174711" Condition="'$(Configuration)' == 'Release'" />
- <ProjectReference Include="..\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj" Condition="'$(Configuration)' == 'Debug'"/>
+ <!-- <PackageReference Include="ArcaneLibs" Version="1.0.0-preview.20250806-011111" Condition="'$(Configuration)' == 'Release'" />-->
+ <!-- <ProjectReference Include="..\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj" Condition="'$(Configuration)' == 'Debug'"/>-->
+ <ProjectReference Include="..\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj" Condition="'$(ContinuousIntegrationBuild)'!='true'"/>
+ <PackageReference Include="ArcaneLibs" Version="*-*" Condition="'$(ContinuousIntegrationBuild)'=='true'"/>
</ItemGroup>
</Project>
diff --git a/LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs b/LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs
index 9602bf3..d1cf8be 100644
--- a/LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs
@@ -34,9 +34,29 @@ public class RoomMessageEventContent : TimelineEventContent {
[JsonPropertyName("info")]
public FileInfoStruct? FileInfo { get; set; }
-
+
[JsonIgnore]
- public string BodyWithoutReplyFallback => Body.Split('\n').SkipWhile(x => x.StartsWith(">")).SkipWhile(x=>x.Trim().Length == 0).Aggregate((x, y) => $"{x}\n{y}");
+ public string BodyWithoutReplyFallback {
+ get {
+ var parts = Body
+ .Split('\n')
+ .SkipWhile(x => x.StartsWith(">"))
+ .SkipWhile(x => x.Trim().Length == 0)
+ .ToList();
+ return parts.Count > 0 ? parts.Aggregate((x, y) => $"{x}\n{y}") : Body;
+ }
+ }
+
+ [JsonPropertyName("m.mentions")]
+ public MentionsStruct? Mentions { get; set; }
+
+ public class MentionsStruct {
+ [JsonPropertyName("user_ids")]
+ public List<string>? Users { get; set; }
+
+ [JsonPropertyName("room")]
+ public bool? Room { get; set; }
+ }
public class FileInfoStruct {
[JsonPropertyName("mimetype")]
@@ -47,10 +67,10 @@ public class RoomMessageEventContent : TimelineEventContent {
[JsonPropertyName("thumbnail_url")]
public string? ThumbnailUrl { get; set; }
-
+
[JsonPropertyName("w")]
public int? Width { get; set; }
-
+
[JsonPropertyName("h")]
public int? Height { get; set; }
}
diff --git a/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs b/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs
index 6f8c194..36c94ae 100644
--- a/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs
@@ -1,5 +1,6 @@
using System.Diagnostics;
using System.Security.Cryptography;
+using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using ArcaneLibs.Attributes;
@@ -35,7 +36,6 @@ public class RoomPolicyRuleEventContent : PolicyRuleEventContent {
[DebuggerDisplay("""{GetType().Name.Replace("PolicyRuleEventContent", ""),nq} policy matching {Entity}, Reason: {Reason}""")]
public abstract class PolicyRuleEventContent : EventContent {
// public PolicyRuleEventContent() => Console.WriteLine($"init policy {GetType().Name}");
- private string? _reason;
/// <summary>
/// Entity this ban applies to, can use * and ? as globs.
@@ -52,17 +52,7 @@ public abstract class PolicyRuleEventContent : EventContent {
/// </summary>
[JsonPropertyName("reason")]
[FriendlyName(Name = "Reason")]
- public virtual string? Reason {
- get =>
- // Console.WriteLine($"Read policy reason: {_reason}");
- _reason;
- set =>
- // Console.WriteLine($"Set policy reason: {value}");
- // if(init)
- // Console.WriteLine(string.Join('\n', Environment.StackTrace.Split('\n')[..5]));
- // init = true;
- _reason = value;
- }
+ public string? Reason { get; set; }
/// <summary>
/// Suggested action to take
@@ -93,29 +83,55 @@ public abstract class PolicyRuleEventContent : EventContent {
}
}
+ [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("?", "."), RegexOptions.Compiled);
+ public bool IsGlobRule() => !string.IsNullOrWhiteSpace(Entity) && (Entity.Contains('*') || Entity.Contains('?'));
+ public bool IsHashedRule() => string.IsNullOrWhiteSpace(Entity) && Hashes is not null;
- public Regex? GetEntityRegex() => Entity is null ? null : new(Entity.Replace(".", "\\.").Replace("*", ".*").Replace("?", "."));
+ public bool EntityMatches(string entity) {
+ if (string.IsNullOrWhiteSpace(entity)) return false;
- public bool IsGlobRule() =>
- Entity != null
- && (Entity.Contains('*') || Entity.Contains('?'));
+ 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;
+ }
- public bool EntityMatches(string entity) =>
- Entity != null
- && (
- Entity == entity
- || (
- IsGlobRule()
- ? GetEntityRegex()!.IsMatch(entity)
- : entity == Entity
- )
- );
+ 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 string? GetSpecRecommendation() {
+ if (Recommendation is "m.ban" or "org.matrix.mjolnir.ban")
+ return PolicyRecommendationTypes.Ban;
+
+ if (Recommendation is "m.mute" or "support.feline.policy.recommendation_mute")
+ return PolicyRecommendationTypes.Mute;
+
+ if (Recommendation is "m.takedown" or "org.matrix.msc4204.takedown")
+ return PolicyRecommendationTypes.Takedown;
+
return Recommendation;
}
}
@@ -130,6 +146,19 @@ public static class PolicyRecommendationTypes {
/// Mute this user
/// </summary>
public static string Mute = "support.feline.policy.recommendation_mute"; //stable prefix: m.mute, msc pending
+
+ /// <summary>
+ /// Take down the user with all means available
+ /// </summary>
+ public static string Takedown = "org.matrix.msc4204.takedown"; //stable prefix: m.takedown, msc pending
+}
+
+public class PolicyHash {
+ [JsonPropertyName("sha256")]
+ public string? Sha256 { get; set; }
+
+ [JsonExtensionData]
+ public Dictionary<string, object>? AdditionalProperties { get; set; }
}
// public class PolicySchemaDefinition {
diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs
index 37b831a..8519889 100644
--- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs
@@ -12,9 +12,14 @@ public class RoomCreateEventContent : EventContent {
[JsonPropertyName("room_version")]
public string? RoomVersion { get; set; }
+ // missing in room version 11+
[JsonPropertyName("creator")]
public string? Creator { get; set; }
+ // v12+
+ [JsonPropertyName("additional_creators")]
+ public List<string>? AdditionalCreators { get; set; }
+
[JsonPropertyName("m.federate")]
public bool? Federate { get; set; }
diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs
index 16cfcb0..3b3ba34 100644
--- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs
@@ -8,4 +8,11 @@ public class RoomHistoryVisibilityEventContent : EventContent {
[JsonPropertyName("history_visibility")]
public required string HistoryVisibility { get; set; }
+
+ public static class HistoryVisibilityTypes {
+ public const string WorldReadable = "world_readable";
+ public const string Invited = "invited";
+ public const string Shared = "shared";
+ public const string Joined = "joined";
+ }
}
\ No newline at end of file
diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPolicyServerEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPolicyServerEventContent.cs
new file mode 100644
index 0000000..80e254f
--- /dev/null
+++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPolicyServerEventContent.cs
@@ -0,0 +1,11 @@
+using System.Text.Json.Serialization;
+
+namespace LibMatrix.EventTypes.Spec.State.RoomInfo;
+
+[MatrixEvent(EventName = EventId)]
+public class RoomPolicyServerEventContent : EventContent {
+ public const string EventId = "org.matrix.msc4284.policy";
+
+ [JsonPropertyName("via")]
+ public string? Via { get; set; }
+}
\ No newline at end of file
diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomRedactionEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomRedactionEventContent.cs
new file mode 100644
index 0000000..055f22d
--- /dev/null
+++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomRedactionEventContent.cs
@@ -0,0 +1,18 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Text.Json.Serialization;
+
+namespace LibMatrix.EventTypes.Spec.State.RoomInfo;
+
+[MatrixEvent(EventName = EventId)]
+public class RoomRedactionEventContent : EventContent {
+ public const string EventId = "m.room.redaction";
+
+ [JsonPropertyName("reason")]
+ public string? Reason { get; set; }
+
+ /// <summary>
+ /// Required in room version 11
+ /// </summary>
+ [JsonPropertyName("redacts")]
+ public string? Redacts { get; set; }
+}
\ No newline at end of file
diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs
index c492250..c7ad491 100644
--- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
+using System.Text.RegularExpressions;
namespace LibMatrix.EventTypes.Spec.State.RoomInfo;
@@ -14,4 +15,10 @@ public class RoomServerAclEventContent : EventContent {
[JsonPropertyName("allow_ip_literals")]
public bool AllowIpLiterals { get; set; } // = false;
+
+ [JsonIgnore]
+ public List<Regex>? AllowRegexes => Allow?.ConvertAll(pattern => new Regex(pattern.Replace(".", "\\.").Replace("*", ".*").Replace("?", "."), RegexOptions.Compiled)) ?? [];
+
+ [JsonIgnore]
+ public List<Regex>? DenyRegexes => Deny?.ConvertAll(pattern => new Regex(pattern.Replace(".", "\\.").Replace("*", ".*").Replace("?", "."), RegexOptions.Compiled)) ?? [];
}
\ No newline at end of file
diff --git a/LibMatrix.EventTypes/deps.json b/LibMatrix.EventTypes/deps.json
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/LibMatrix.EventTypes/deps.json
|