diff --git a/ArcaneLibs b/ArcaneLibs
-Subproject fef0025c87017e1fd25b59f464a354fea746bc6
+Subproject d3f0ba9cb7ec36ab2f9574fd563c467b61512b9
diff --git a/LibMatrix/Helpers/MessageBuilder.cs b/LibMatrix/Helpers/MessageBuilder.cs
index 6f55739..f753bf7 100644
--- a/LibMatrix/Helpers/MessageBuilder.cs
+++ b/LibMatrix/Helpers/MessageBuilder.cs
@@ -95,8 +95,13 @@ public class MessageBuilder(string msgType = "m.text", string format = "org.matr
return this;
}
- public MessageBuilder WithMention(string id, string? displayName = null) {
- Content.Body += $"@{displayName ?? id}";
+ public MessageBuilder WithMention(string id, string? displayName = null, string[]? vias = null, bool useIdInPlainText = false, bool useLinkInPlainText = false) {
+ if (!useLinkInPlainText) Content.Body += $"@{(useIdInPlainText ? id : displayName ?? id)}";
+ else {
+ Content.Body += $"https://matrix.to/#/{id}";
+ if (vias is { Length: > 0 }) Content.Body += $"?via={string.Join("&via=", vias)}";
+ }
+
Content.FormattedBody += $"<a href=\"https://matrix.to/#/{id}\">{displayName ?? id}</a>";
if (id == "@room") {
Content.Mentions ??= new();
@@ -111,6 +116,15 @@ public class MessageBuilder(string msgType = "m.text", string format = "org.matr
return this;
}
+ public MessageBuilder WithRoomMention() {
+ // Legacy push rules support
+ Content.Body += "@room";
+ Content.FormattedBody += "@room";
+ Content.Mentions ??= new();
+ Content.Mentions.Room = true;
+ return this;
+ }
+
public MessageBuilder WithNewline() {
Content.Body += "\n";
Content.FormattedBody += "<br>";
diff --git a/LibMatrix/Helpers/RoomBuilder.cs b/LibMatrix/Helpers/RoomBuilder.cs
index ae01d11..8843a21 100644
--- a/LibMatrix/Helpers/RoomBuilder.cs
+++ b/LibMatrix/Helpers/RoomBuilder.cs
@@ -1,4 +1,5 @@
using System.Runtime.Intrinsics.X86;
+using ArcaneLibs.Extensions;
using LibMatrix.EventTypes.Spec.State.RoomInfo;
using LibMatrix.Homeservers;
using LibMatrix.Responses;
@@ -34,6 +35,8 @@ public class RoomBuilder {
AllowIpLiterals = false
};
+ public RoomEncryptionEventContent? Encryption { get; set; }
+
/// <summary>
/// State events to be sent *before* room access is configured. Keep this small!
/// </summary>
@@ -47,7 +50,12 @@ public class RoomBuilder {
/// <summary>
/// Users to invite, with optional reason
/// </summary>
- public Dictionary<string, string?> Invites { get; set; } = new();
+ public Dictionary<string, string?> Invites { get; set; } = [];
+
+ /// <summary>
+ /// Users to ban, with optional reason
+ /// </summary>
+ public Dictionary<string, string?> Bans { get; set; } = [];
public RoomPowerLevelEventContent PowerLevels { get; set; } = new() {
EventsDefault = 0,
@@ -184,6 +192,13 @@ public class RoomBuilder {
private async Task SetAccessAsync(GenericRoom room) {
if(!V12PlusRoomVersions.Contains(Version))
PowerLevels.Users![room.Homeserver.WhoAmI.UserId] = OwnPowerLevel;
+ else {
+ PowerLevels.Users!.Remove(room.Homeserver.WhoAmI.UserId);
+ foreach (var additionalCreator in AdditionalCreators)
+ {
+ PowerLevels.Users!.Remove(additionalCreator);
+ }
+ }
await room.SendStateEventAsync(RoomPowerLevelEventContent.EventId, PowerLevels);
if (!string.IsNullOrWhiteSpace(HistoryVisibility.HistoryVisibility))
diff --git a/LibMatrix/Helpers/RoomUpgradeBuilder.cs b/LibMatrix/Helpers/RoomUpgradeBuilder.cs
new file mode 100644
index 0000000..cbc7876
--- /dev/null
+++ b/LibMatrix/Helpers/RoomUpgradeBuilder.cs
@@ -0,0 +1,81 @@
+using System.Text.Json.Serialization;
+using LibMatrix.EventTypes.Spec;
+using LibMatrix.EventTypes.Spec.State.RoomInfo;
+using LibMatrix.RoomTypes;
+
+namespace LibMatrix.Helpers;
+
+public class RoomUpgradeBuilder(GenericRoom oldRoom) : RoomBuilder {
+ public GenericRoom OldRoom { get; } = oldRoom;
+ public RoomUpgradeOptions UpgradeOptions { get; set; } = new();
+
+ public async Task ImportAsync() {
+ await foreach (var evt in OldRoom.GetFullStateAsync()) {
+ if (evt is null) continue;
+ if (evt.StateKey == "") {
+ if (evt.TypedContent is RoomCreateEventContent createEvt)
+ foreach (var (key, value) in evt.RawContent) {
+ if (key == "version") continue;
+ if (key == "type")
+ Type = value!.GetValue<string>();
+ else AdditionalCreationContent[key] = value;
+ }
+ else if (evt.TypedContent is RoomNameEventContent name)
+ Name = name;
+ else if (evt.TypedContent is RoomTopicEventContent topic)
+ Topic = topic;
+ else if (evt.TypedContent is RoomAvatarEventContent avatar)
+ Avatar = avatar;
+ else if (evt.TypedContent is RoomCanonicalAliasEventContent alias) {
+ CanonicalAlias = alias;
+ AliasLocalPart = alias.Alias?.Split(':',2).FirstOrDefault()?[1..] ?? string.Empty;
+ }
+ else if (evt.TypedContent is RoomJoinRulesEventContent joinRules)
+ JoinRules = joinRules;
+ else if (evt.TypedContent is RoomHistoryVisibilityEventContent historyVisibility)
+ HistoryVisibility = historyVisibility;
+ else if (evt.TypedContent is RoomGuestAccessEventContent guestAccess)
+ GuestAccess = guestAccess;
+ else if (evt.TypedContent is RoomServerAclEventContent serverAcls)
+ ServerAcls = serverAcls;
+ else if (evt.TypedContent is RoomPowerLevelEventContent powerLevels) {
+ if (UpgradeOptions.InvitePowerlevelUsers && powerLevels.Users != null)
+ foreach (var (userId, level) in powerLevels.Users)
+ if (level > powerLevels.UsersDefault)
+ Invites.Add(userId, "Room upgrade (had a power level)");
+
+ PowerLevels = powerLevels;
+ }
+ else if (evt.TypedContent is RoomEncryptionEventContent encryption)
+ Encryption = encryption;
+ else if (evt.TypedContent is RoomPinnedEventContent) ; // Discard as you can't cross reference pinned events
+ else InitialState.Add(evt);
+ }
+ else if (evt.Type == RoomMemberEventContent.EventId) {
+ if (UpgradeOptions.InviteMembers && evt.TypedContent is RoomMemberEventContent { Membership: "join" or "invite" })
+ if (!Invites.ContainsKey(evt.StateKey))
+ Invites.Add(evt.StateKey, "Room upgrade");
+ else if (UpgradeOptions.MigrateBans && evt.TypedContent is RoomMemberEventContent { Membership: "ban" } bannedMember)
+ Bans.Add(evt.StateKey, bannedMember.Reason);
+ }
+ else InitialState.Add(evt);
+ }
+ }
+
+ public class RoomUpgradeOptions {
+ public bool InviteMembers { get; set; }
+ public bool InvitePowerlevelUsers { get; set; }
+ public bool MigrateBans { get; set; }
+
+ [JsonIgnore]
+ public Func<GenericRoom, Task<RoomMessageEventContent>> RoomUpgradeNotice { get; set; } = async newRoom => new MessageBuilder()
+ .WithRoomMention()
+ .WithBody("This room has been upgraded to a new version. This version of the room will be kept as an archive.")
+ .WithNewline()
+ .WithBody("You can join the new room by clicking the link below:")
+ .WithNewline()
+ .WithRoomMention()
+ .WithMention(newRoom.RoomId, await newRoom.GetNameOrFallbackAsync(), vias: (await newRoom.GetHomeserversInRoom()).ToArray(), useLinkInPlainText: true)
+ .Build();
+ }
+}
\ No newline at end of file
diff --git a/LibMatrix/Responses/CreateRoomRequest.cs b/LibMatrix/Responses/CreateRoomRequest.cs
index 8f8af31..db7d004 100644
--- a/LibMatrix/Responses/CreateRoomRequest.cs
+++ b/LibMatrix/Responses/CreateRoomRequest.cs
@@ -47,6 +47,7 @@ public class CreateRoomRequest {
[JsonPropertyName("invite")]
public List<string>? Invite { get; set; }
+ [JsonPropertyName("room_version")]
public string? RoomVersion { get; set; }
/// <summary>
diff --git a/LibMatrix/RoomTypes/GenericRoom.cs b/LibMatrix/RoomTypes/GenericRoom.cs
index 565776c..7fc942e 100644
--- a/LibMatrix/RoomTypes/GenericRoom.cs
+++ b/LibMatrix/RoomTypes/GenericRoom.cs
@@ -695,6 +695,8 @@ public class GenericRoom {
return creators[0].Split(':', 2)[1];
}
+
+ public async Task<List<string>> GetHomeserversInRoom() => (await GetMemberIdsListAsync("join")).Select(x => x.Split(':', 2)[1]).Distinct().ToList();
}
public class RoomIdResponse {
|