diff --git a/ExampleBots/LibMatrix.ExampleBot/Bot/MRUBot.cs b/ExampleBots/LibMatrix.ExampleBot/Bot/MRUBot.cs
index f04ec3a..0211f74 100644
--- a/ExampleBots/LibMatrix.ExampleBot/Bot/MRUBot.cs
+++ b/ExampleBots/LibMatrix.ExampleBot/Bot/MRUBot.cs
@@ -57,7 +57,7 @@ public class MRUBot : IHostedService {
hs.SyncHelper.InviteReceivedHandlers.Add(async Task (args) => {
var inviteEvent =
args.Value.InviteState.Events.FirstOrDefault(x =>
- x.Type == "m.room.member" && x.StateKey == hs.WhoAmI.UserId);
+ x.Type == "m.room.member" && x.StateKey == hs.UserId);
_logger.LogInformation(
$"Got invite to {args.Key} by {inviteEvent.Sender} with reason: {(inviteEvent.TypedContent as RoomMemberEventContent).Reason}");
if (inviteEvent.Sender.EndsWith(":rory.gay") || inviteEvent.Sender == "@mxidupwitch:the-apothecary.club") {
diff --git a/ExampleBots/MediaModeratorPoC/Bot/Commands/BanMediaCommand.cs b/ExampleBots/MediaModeratorPoC/Bot/Commands/BanMediaCommand.cs
index d633f89..fd6866c 100644
--- a/ExampleBots/MediaModeratorPoC/Bot/Commands/BanMediaCommand.cs
+++ b/ExampleBots/MediaModeratorPoC/Bot/Commands/BanMediaCommand.cs
@@ -43,7 +43,7 @@ public class BanMediaCommand(IServiceProvider services, HomeserverProviderServic
messageType: "m.text"));
//get replied message
- var repliedMessage = await ctx.Room.GetEvent<StateEventResponse>(messageContent.RelatesTo!.InReplyTo!.EventId);
+ var repliedMessage = await ctx.Room.GetEventAsync<StateEventResponse>(messageContent.RelatesTo!.InReplyTo!.EventId);
//check if recommendation is in list
if (ctx.Args.Length < 2) {
diff --git a/ExampleBots/MediaModeratorPoC/Bot/MediaModBot.cs b/ExampleBots/MediaModeratorPoC/Bot/MediaModBot.cs
index e6ba269..f9bbcf3 100644
--- a/ExampleBots/MediaModeratorPoC/Bot/MediaModBot.cs
+++ b/ExampleBots/MediaModeratorPoC/Bot/MediaModBot.cs
@@ -109,17 +109,17 @@ public class MediaModBot(AuthenticatedHomeserverGeneric hs, ILogger<MediaModBot>
hs.SyncHelper.InviteReceivedHandlers.Add(async Task (args) => {
var inviteEvent =
args.Value.InviteState.Events.FirstOrDefault(x =>
- x.Type == "m.room.member" && x.StateKey == hs.WhoAmI.UserId);
+ x.Type == "m.room.member" && x.StateKey == hs.UserId);
logger.LogInformation(
$"Got invite to {args.Key} by {inviteEvent.Sender} with reason: {(inviteEvent.TypedContent as RoomMemberEventContent).Reason}");
if (inviteEvent.Sender.EndsWith(":rory.gay") || inviteEvent.Sender.EndsWith(":conduit.rory.gay")) {
try {
var senderProfile = await hs.GetProfileAsync(inviteEvent.Sender);
- await (hs.GetRoom(args.Key)).JoinAsync(reason: $"I was invited by {senderProfile.DisplayName ?? inviteEvent.Sender}!");
+ await hs.GetRoom(args.Key).JoinAsync(reason: $"I was invited by {senderProfile.DisplayName ?? inviteEvent.Sender}!");
}
catch (Exception e) {
logger.LogError("{}", e.ToString());
- await (hs.GetRoom(args.Key)).LeaveAsync(reason: "I was unable to join the room: " + e);
+ await hs.GetRoom(args.Key).LeaveAsync(reason: "I was unable to join the room: " + e);
}
}
});
@@ -161,16 +161,16 @@ public class MediaModBot(AuthenticatedHomeserverGeneric hs, ILogger<MediaModBot>
case "warn": {
await room.SendMessageEventAsync(
new RoomMessageEventContent(
- body: $"Please be careful when posting this image: {matchedpolicyData.Reason}",
+ body: $"Please be careful when posting this image: {matchedpolicyData.Reason ?? "No reason specified"}",
messageType: "m.text") {
Format = "org.matrix.custom.html",
FormattedBody =
- $"<font color=\"#FFFF00\">Please be careful when posting this image: {matchedpolicyData.Reason}</a></font>"
+ $"<font color=\"#FFFF00\">Please be careful when posting this image: {matchedpolicyData.Reason ?? "No reason specified"}</a></font>"
});
break;
}
case "redact": {
- await room.RedactEventAsync(@event.EventId, matchedpolicyData.Reason);
+ await room.RedactEventAsync(@event.EventId, matchedpolicyData.Reason ?? "No reason specified");
break;
}
case "spoiler": {
@@ -220,9 +220,15 @@ public class MediaModBot(AuthenticatedHomeserverGeneric hs, ILogger<MediaModBot>
await room.RedactEventAsync(@event.EventId, matchedpolicyData.Reason);
//change powerlevel to -1
var currentPls = await room.GetPowerLevelsAsync();
+ if(currentPls is null) {
+ logger.LogWarning("Unable to get power levels for {room}", room.RoomId);
+ await _logRoom.SendMessageEventAsync(
+ MessageFormatter.FormatError($"Unable to get power levels for {MessageFormatter.HtmlFormatMention(room.RoomId)}"));
+ return;
+ }
+ currentPls.Users ??= new();
currentPls.Users[@event.Sender] = -1;
await room.SendStateEventAsync("m.room.power_levels", currentPls);
-
break;
}
case "kick": {
diff --git a/ExampleBots/MediaModeratorPoC/Bot/StateEventTypes/MediaPolicyStateEventData.cs b/ExampleBots/MediaModeratorPoC/Bot/StateEventTypes/MediaPolicyStateEventData.cs
index 6686a37..0096c78 100644
--- a/ExampleBots/MediaModeratorPoC/Bot/StateEventTypes/MediaPolicyStateEventData.cs
+++ b/ExampleBots/MediaModeratorPoC/Bot/StateEventTypes/MediaPolicyStateEventData.cs
@@ -4,7 +4,8 @@ using LibMatrix.Interfaces;
namespace MediaModeratorPoC.Bot.StateEventTypes;
-[MatrixEvent(EventName = "gay.rory.media_moderator_poc.rule.homeserver")]
+[
+ MatrixEvent(EventName = "gay.rory.media_moderator_poc.rule.homeserver")]
[MatrixEvent(EventName = "gay.rory.media_moderator_poc.rule.media")]
public class MediaPolicyEventContent : EventContent {
/// <summary>
diff --git a/ExampleBots/PluralContactBotPoC/Bot/PluralContactBot.cs b/ExampleBots/PluralContactBotPoC/Bot/PluralContactBot.cs
index c3cebe2..0bd2bbf 100644
--- a/ExampleBots/PluralContactBotPoC/Bot/PluralContactBot.cs
+++ b/ExampleBots/PluralContactBotPoC/Bot/PluralContactBot.cs
@@ -41,7 +41,7 @@ public class PluralContactBot(AuthenticatedHomeserverGeneric hs, ILogger<PluralC
hs.SyncHelper.InviteReceivedHandlers.Add(async Task (args) => {
var inviteEvent =
args.Value.InviteState.Events.FirstOrDefault(x =>
- x.Type == "m.room.member" && x.StateKey == hs.WhoAmI.UserId);
+ x.Type == "m.room.member" && x.StateKey == hs.UserId);
logger.LogInformation("Got invite to {} by {} with reason: {}", args.Key, inviteEvent.Sender, (inviteEvent.TypedContent as RoomMemberEventContent).Reason);
try {
diff --git a/LibMatrix/EventTypes/Spec/RoomMessageEventData.cs b/LibMatrix/EventTypes/Spec/RoomMessageEventData.cs
index b76b176..f8ee58b 100644
--- a/LibMatrix/EventTypes/Spec/RoomMessageEventData.cs
+++ b/LibMatrix/EventTypes/Spec/RoomMessageEventData.cs
@@ -28,4 +28,6 @@ public class RoomMessageEventContent : EventContent {
/// </summary>
[JsonPropertyName("url")]
public string? Url { get; set; }
+
+ public string? FileName { get; set; }
}
diff --git a/LibMatrix/EventTypes/Spec/State/RoomCreateEventData.cs b/LibMatrix/EventTypes/Spec/State/RoomCreateEventData.cs
index e409f3a..c5bf14e 100644
--- a/LibMatrix/EventTypes/Spec/State/RoomCreateEventData.cs
+++ b/LibMatrix/EventTypes/Spec/State/RoomCreateEventData.cs
@@ -8,12 +8,16 @@ namespace LibMatrix.EventTypes.Spec.State;
public class RoomCreateEventContent : EventContent {
[JsonPropertyName("room_version")]
public string? RoomVersion { get; set; }
+
[JsonPropertyName("creator")]
public string? Creator { get; set; }
+
[JsonPropertyName("m.federate")]
public bool? Federate { get; set; }
+
[JsonPropertyName("predecessor")]
public RoomCreatePredecessor? Predecessor { get; set; }
+
[JsonPropertyName("type")]
public string? Type { get; set; }
diff --git a/LibMatrix/EventTypes/Spec/State/RoomPowerLevelEventData.cs b/LibMatrix/EventTypes/Spec/State/RoomPowerLevelEventData.cs
index 1a5d5f5..2ae9593 100644
--- a/LibMatrix/EventTypes/Spec/State/RoomPowerLevelEventData.cs
+++ b/LibMatrix/EventTypes/Spec/State/RoomPowerLevelEventData.cs
@@ -7,34 +7,34 @@ namespace LibMatrix.EventTypes.Spec.State;
[MatrixEvent(EventName = "m.room.power_levels")]
public class RoomPowerLevelEventContent : EventContent {
[JsonPropertyName("ban")]
- public long? Ban { get; set; } // = 50;
+ public long? Ban { get; set; } = 50;
[JsonPropertyName("events_default")]
- public long EventsDefault { get; set; } // = 0;
+ public long EventsDefault { get; set; } = 0;
[JsonPropertyName("events")]
public Dictionary<string, long>? Events { get; set; } // = null!;
[JsonPropertyName("invite")]
- public long? Invite { get; set; } // = 50;
+ public long? Invite { get; set; } = 0;
[JsonPropertyName("kick")]
- public long? Kick { get; set; } // = 50;
+ public long? Kick { get; set; } = 50;
[JsonPropertyName("notifications")]
public NotificationsPL? NotificationsPl { get; set; } // = null!;
[JsonPropertyName("redact")]
- public long? Redact { get; set; } // = 50;
+ public long? Redact { get; set; } = 50;
[JsonPropertyName("state_default")]
- public long? StateDefault { get; set; } // = 50;
+ public long? StateDefault { get; set; } = 50;
[JsonPropertyName("users")]
public Dictionary<string, long>? Users { get; set; } // = null!;
[JsonPropertyName("users_default")]
- public long? UsersDefault { get; set; } // = 0;
+ public long? UsersDefault { get; set; } = 0;
[Obsolete("Historical was a key related to MSC2716, a spec change on backfill that was dropped!", true)]
[JsonIgnore]
@@ -47,7 +47,7 @@ public class RoomPowerLevelEventContent : EventContent {
}
public bool IsUserAdmin(string userId) {
- return Users.TryGetValue(userId, out var level) && level >= Events.Max(x=>x.Value);
+ return Users.TryGetValue(userId, out var level) && level >= Events.Max(x => x.Value);
}
public bool UserHasPermission(string userId, string eventType) {
diff --git a/LibMatrix/EventTypes/Spec/State/SpaceChildEventData.cs b/LibMatrix/EventTypes/Spec/State/SpaceChildEventData.cs
index a13ba2e..0a897dc 100644
--- a/LibMatrix/EventTypes/Spec/State/SpaceChildEventData.cs
+++ b/LibMatrix/EventTypes/Spec/State/SpaceChildEventData.cs
@@ -9,7 +9,7 @@ public class SpaceChildEventContent : EventContent {
[JsonPropertyName("auto_join")]
public bool? AutoJoin { get; set; }
[JsonPropertyName("via")]
- public string[]? Via { get; set; }
+ public List<string>? Via { get; set; }
[JsonPropertyName("suggested")]
public bool? Suggested { get; set; }
}
diff --git a/LibMatrix/Helpers/SyncHelper.cs b/LibMatrix/Helpers/SyncHelper.cs
index 386fd4d..74972a1 100644
--- a/LibMatrix/Helpers/SyncHelper.cs
+++ b/LibMatrix/Helpers/SyncHelper.cs
@@ -16,10 +16,6 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver) {
string? setPresence = "online",
SyncFilter? filter = null,
CancellationToken? cancellationToken = null) {
- // var outFileName = "sync-" +
- // (await storageService.CacheStorageProvider.GetAllKeysAsync()).Count(
- // x => x.StartsWith("sync")) +
- // ".json";
var url = $"/_matrix/client/v3/sync?timeout={timeout}&set_presence={setPresence}";
if (!string.IsNullOrWhiteSpace(since)) url += $"&since={since}";
if (filter is not null) url += $"&filter={filter.ToJson(ignoreNull: true, indent: false)}";
@@ -28,19 +24,17 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver) {
try {
var req = await homeserver._httpClient.GetAsync(url, cancellationToken: cancellationToken ?? CancellationToken.None);
- // var res = await JsonSerializer.DeserializeAsync<SyncResult>(await req.Content.ReadAsStreamAsync());
-
#if DEBUG && false
- var jsonObj = await req.Content.ReadFromJsonAsync<JsonElement>();
try {
- await _homeServer._httpClient.PostAsJsonAsync(
- "http://localhost:5116/validate/" + typeof(SyncResult).AssemblyQualifiedName, jsonObj);
+ await homeserver._httpClient.PostAsync(
+ "http://localhost:5116/validate/" + typeof(SyncResult).AssemblyQualifiedName,
+ new StreamContent(await req.Content.ReadAsStreamAsync()));
}
+
catch (Exception e) {
Console.WriteLine("[!!] Checking sync response failed: " + e);
}
-
- var res = jsonObj.Deserialize<SyncResult>();
+ var res = await req.Content.ReadFromJsonAsync<SyncResult>();
return res;
#else
return await req.Content.ReadFromJsonAsync<SyncResult>();
diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
index b881e6c..f70dd39 100644
--- a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
+++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
@@ -1,3 +1,4 @@
+using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Nodes;
@@ -12,18 +13,30 @@ using LibMatrix.Services;
namespace LibMatrix.Homeservers;
public class AuthenticatedHomeserverGeneric : RemoteHomeServer {
- public AuthenticatedHomeserverGeneric(string canonicalHomeServerDomain, string accessToken) : base(canonicalHomeServerDomain) {
+ public AuthenticatedHomeserverGeneric(string baseUrl, string accessToken) : base(baseUrl) {
AccessToken = accessToken.Trim();
SyncHelper = new SyncHelper(this);
+
+ _httpClient.Timeout = TimeSpan.FromMinutes(15);
+ _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
}
public virtual SyncHelper SyncHelper { get; init; }
- public virtual WhoAmIResponse WhoAmI { get; set; } = null!;
- public virtual string UserId => WhoAmI.UserId;
+ private WhoAmIResponse? _whoAmI;
+
+ public WhoAmIResponse? WhoAmI => _whoAmI ??= _httpClient.GetFromJsonAsync<WhoAmIResponse>("/_matrix/client/v3/account/whoami").Result;
+ public string UserId => WhoAmI.UserId;
+
+ // public virtual async Task<WhoAmIResponse> WhoAmI() {
+ // if (_whoAmI is not null) return _whoAmI;
+ // _whoAmI = await _httpClient.GetFromJsonAsync<WhoAmIResponse>("/_matrix/client/v3/account/whoami");
+ // return _whoAmI;
+ // }
+
public virtual string AccessToken { get; set; }
public virtual GenericRoom GetRoom(string roomId) {
- if(roomId is null || !roomId.StartsWith("!")) throw new ArgumentException("Room ID must start with !", nameof(roomId));
+ if (roomId is null || !roomId.StartsWith("!")) throw new ArgumentException("Room ID must start with !", nameof(roomId));
return new GenericRoom(this, roomId);
}
@@ -50,7 +63,7 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeServer {
}
public virtual async Task<GenericRoom> CreateRoom(CreateRoomRequest creationEvent) {
- creationEvent.CreationContent["creator"] = UserId;
+ creationEvent.CreationContent["creator"] = WhoAmI.UserId;
var res = await _httpClient.PostAsJsonAsync("/_matrix/client/v3/createRoom", creationEvent, new JsonSerializerOptions {
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
});
@@ -61,9 +74,10 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeServer {
var room = GetRoom((await res.Content.ReadFromJsonAsync<JsonObject>())!["room_id"]!.ToString());
- foreach (var user in creationEvent.Invite) {
- await room.InviteUser(user);
- }
+ if (creationEvent.Invite is not null)
+ foreach (var user in creationEvent.Invite) {
+ await room.InviteUserAsync(user);
+ }
return room;
}
@@ -77,6 +91,7 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeServer {
}
#region Utility Functions
+
public virtual async IAsyncEnumerable<GenericRoom> GetJoinedRoomsByType(string type) {
var rooms = await GetJoinedRooms();
var tasks = rooms.Select(async room => {
@@ -92,6 +107,7 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeServer {
if (result is not null) yield return result;
}
}
+
#endregion
#region Account Data
@@ -104,11 +120,11 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeServer {
// }
//
// return await res.Content.ReadFromJsonAsync<T>();
- return await _httpClient.GetFromJsonAsync<T>($"/_matrix/client/v3/user/{UserId}/account_data/{key}");
+ return await _httpClient.GetFromJsonAsync<T>($"/_matrix/client/v3/user/{WhoAmI.UserId}/account_data/{key}");
}
public virtual async Task SetAccountData(string key, object data) {
- var res = await _httpClient.PutAsJsonAsync($"/_matrix/client/v3/user/{UserId}/account_data/{key}", data);
+ var res = await _httpClient.PutAsJsonAsync($"/_matrix/client/v3/user/{WhoAmI.UserId}/account_data/{key}", data);
if (!res.IsSuccessStatusCode) {
Console.WriteLine($"Failed to set account data: {await res.Content.ReadAsStringAsync()}");
throw new InvalidDataException($"Failed to set account data: {await res.Content.ReadAsStringAsync()}");
@@ -116,4 +132,6 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeServer {
}
#endregion
+
+
}
diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverMxApiExtended.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverMxApiExtended.cs
index 5319f46..a420d71 100644
--- a/LibMatrix/Homeservers/AuthenticatedHomeserverMxApiExtended.cs
+++ b/LibMatrix/Homeservers/AuthenticatedHomeserverMxApiExtended.cs
@@ -4,5 +4,4 @@ using LibMatrix.Services;
namespace LibMatrix.Homeservers;
-public class AuthenticatedHomeserverMxApiExtended(string canonicalHomeServerDomain, string accessToken) : AuthenticatedHomeserverGeneric(canonicalHomeServerDomain,
- accessToken);
+public class AuthenticatedHomeserverMxApiExtended(string baseUrl, string accessToken) : AuthenticatedHomeserverGeneric(baseUrl, accessToken);
diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs
index ae26f69..e355d2d 100644
--- a/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs
+++ b/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs
@@ -102,7 +102,7 @@ public class AuthenticatedHomeserverSynapse : AuthenticatedHomeserverGeneric {
}
}
- public AuthenticatedHomeserverSynapse(string canonicalHomeServerDomain, string accessToken) : base(canonicalHomeServerDomain, accessToken) {
+ public AuthenticatedHomeserverSynapse(string baseUrl, string accessToken) : base(baseUrl, accessToken) {
Admin = new(this);
}
}
diff --git a/LibMatrix/Homeservers/RemoteHomeServer.cs b/LibMatrix/Homeservers/RemoteHomeServer.cs
index ab3ab51..d10c837 100644
--- a/LibMatrix/Homeservers/RemoteHomeServer.cs
+++ b/LibMatrix/Homeservers/RemoteHomeServer.cs
@@ -1,19 +1,22 @@
using System.Net.Http.Json;
+using System.Text.Json;
using System.Text.Json.Serialization;
using ArcaneLibs.Extensions;
using LibMatrix.EventTypes.Spec.State;
using LibMatrix.Extensions;
using LibMatrix.Responses;
+using LibMatrix.Services;
namespace LibMatrix.Homeservers;
-public class RemoteHomeServer(string canonicalHomeServerDomain) {
- // _httpClient.Timeout = TimeSpan.FromSeconds(5);
+public class RemoteHomeServer(string baseUrl) {
private Dictionary<string, object> _profileCache { get; set; } = new();
- public string HomeServerDomain { get; } = canonicalHomeServerDomain.Trim();
- public string FullHomeServerDomain { get; set; }
- public MatrixHttpClient _httpClient { get; set; } = new();
+ public string BaseUrl { get; } = baseUrl.Trim();
+ public MatrixHttpClient _httpClient { get; set; } = new() {
+ BaseAddress = new Uri(new HomeserverResolverService().ResolveHomeserverFromWellKnown(baseUrl).Result ?? throw new InvalidOperationException("Failed to resolve homeserver")),
+ Timeout = TimeSpan.FromSeconds(120)
+ };
public async Task<ProfileResponseEventContent> GetProfileAsync(string mxid) {
if (mxid is null) throw new ArgumentNullException(nameof(mxid));
@@ -46,6 +49,42 @@ public class RemoteHomeServer(string canonicalHomeServerDomain) {
if (!resp.IsSuccessStatusCode) Console.WriteLine("ResolveAlias: " + data.ToJson());
return data;
}
+
+#region Authentication
+
+ public async Task<LoginResponse> LoginAsync(string username, string password, string? deviceName = null) {
+ var resp = await _httpClient.PostAsJsonAsync("/_matrix/client/r0/login", new {
+ type = "m.login.password",
+ identifier = new {
+ type = "m.id.user",
+ user = username
+ },
+ password = password,
+ initial_device_display_name = deviceName
+ });
+ var data = await resp.Content.ReadFromJsonAsync<LoginResponse>();
+ if (!resp.IsSuccessStatusCode) Console.WriteLine("Login: " + data.ToJson());
+ return data;
+ }
+
+ public async Task<LoginResponse> RegisterAsync(string username, string password, string? deviceName = null) {
+ var resp = await _httpClient.PostAsJsonAsync("/_matrix/client/r0/register", new {
+ kind = "user",
+ auth = new {
+ type = "m.login.dummy"
+ },
+ username,
+ password,
+ initial_device_display_name = deviceName ?? "LibMatrix"
+ }, new JsonSerializerOptions() {
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
+ });
+ var data = await resp.Content.ReadFromJsonAsync<LoginResponse>();
+ if (!resp.IsSuccessStatusCode) Console.WriteLine("Register: " + data.ToJson());
+ return data;
+ }
+
+#endregion
}
public class AliasResult {
diff --git a/LibMatrix/Responses/CreateRoomRequest.cs b/LibMatrix/Responses/CreateRoomRequest.cs
index 381271b..511b3da 100644
--- a/LibMatrix/Responses/CreateRoomRequest.cs
+++ b/LibMatrix/Responses/CreateRoomRequest.cs
@@ -40,7 +40,7 @@ public class CreateRoomRequest {
public JsonObject CreationContent { get; set; } = new();
[JsonPropertyName("invite")]
- public List<string> Invite { get; set; }
+ public List<string>? Invite { get; set; }
/// <summary>
/// For use only when you can't use the CreationContent property
diff --git a/LibMatrix/Responses/LoginResponse.cs b/LibMatrix/Responses/LoginResponse.cs
index 175f337..eb53c0a 100644
--- a/LibMatrix/Responses/LoginResponse.cs
+++ b/LibMatrix/Responses/LoginResponse.cs
@@ -1,19 +1,30 @@
using System.Text.Json.Serialization;
+using LibMatrix.Homeservers;
+using LibMatrix.Services;
namespace LibMatrix.Responses;
public class LoginResponse {
[JsonPropertyName("access_token")]
- public string AccessToken { get; set; }
+ public string AccessToken { get; set; } = null!;
[JsonPropertyName("device_id")]
- public string DeviceId { get; set; }
+ public string DeviceId { get; set; } = null!;
+
+ private string? _homeserver;
[JsonPropertyName("home_server")]
- public string Homeserver { get; set; }
+ public string Homeserver {
+ get => _homeserver ?? UserId.Split(':', 2).Last();
+ protected init => _homeserver = value;
+ }
[JsonPropertyName("user_id")]
- public string UserId { get; set; }
+ public string UserId { get; set; } = null!;
+
+ public async Task<AuthenticatedHomeserverGeneric> GetAuthenticatedHomeserver(string? proxy = null) {
+ return new AuthenticatedHomeserverGeneric(proxy ?? await new HomeserverResolverService().ResolveHomeserverFromWellKnown(Homeserver), AccessToken);
+ }
}
public class LoginRequest {
[JsonPropertyName("type")]
diff --git a/LibMatrix/RoomTypes/GenericRoom.cs b/LibMatrix/RoomTypes/GenericRoom.cs
index ab748fe..78a0873 100644
--- a/LibMatrix/RoomTypes/GenericRoom.cs
+++ b/LibMatrix/RoomTypes/GenericRoom.cs
@@ -28,14 +28,6 @@ public class GenericRoom {
public string RoomId { get; set; }
- [Obsolete("", true)]
- public async Task<JsonElement?> GetStateAsync(string type, string stateKey = "") {
- var url = $"/_matrix/client/v3/rooms/{RoomId}/state";
- if (!string.IsNullOrEmpty(type)) url += $"/{type}";
- if (!string.IsNullOrEmpty(stateKey)) url += $"/{stateKey}";
- return await _httpClient.GetFromJsonAsync<JsonElement>(url);
- }
-
public async IAsyncEnumerable<StateEventResponse?> GetFullStateAsync() {
var res = await _httpClient.GetAsync($"/_matrix/client/v3/rooms/{RoomId}/state");
var result =
@@ -68,7 +60,7 @@ public class GenericRoom {
}
catch (MatrixException e) {
// if (e is not { ErrorCodode: "M_NOT_FOUND" }) {
- throw;
+ throw;
// }
// Console.WriteLine(e);
@@ -202,21 +194,18 @@ public class GenericRoom {
return resu;
}
- public async Task<EventIdResponse> SendFileAsync(string eventType, string fileName, Stream fileStream) {
- var content = new MultipartFormDataContent();
- content.Add(new StreamContent(fileStream), "file", fileName);
- var res = await
- (
- await _httpClient.PutAsync(
- $"/_matrix/client/v3/rooms/{RoomId}/send/{eventType}/" + Guid.NewGuid(),
- content
- )
- )
- .Content.ReadFromJsonAsync<EventIdResponse>();
- return res;
+ public async Task<EventIdResponse> SendFileAsync(string fileName, Stream fileStream, string messageType = "m.file") {
+ var url = await Homeserver.UploadFile(fileName, fileStream);
+ var content = new RoomMessageEventContent() {
+ MessageType = messageType,
+ Url = url,
+ Body = fileName,
+ FileName = fileName,
+ };
+ return await SendTimelineEventAsync("m.room.message", content);
}
- public async Task<T> GetRoomAccountData<T>(string key) {
+ public async Task<T> GetRoomAccountDataAsync<T>(string key) {
var res = await _httpClient.GetAsync($"/_matrix/client/v3/user/{Homeserver.UserId}/rooms/{RoomId}/account_data/{key}");
if (!res.IsSuccessStatusCode) {
Console.WriteLine($"Failed to get room account data: {await res.Content.ReadAsStringAsync()}");
@@ -226,7 +215,7 @@ public class GenericRoom {
return await res.Content.ReadFromJsonAsync<T>();
}
- public async Task SetRoomAccountData(string key, object data) {
+ public async Task SetRoomAccountDataAsync(string key, object data) {
var res = await _httpClient.PutAsJsonAsync($"/_matrix/client/v3/user/{Homeserver.UserId}/rooms/{RoomId}/account_data/{key}", data);
if (!res.IsSuccessStatusCode) {
Console.WriteLine($"Failed to set room account data: {await res.Content.ReadAsStringAsync()}");
@@ -236,7 +225,7 @@ public class GenericRoom {
public readonly SpaceRoom AsSpace;
- public async Task<T> GetEvent<T>(string eventId) {
+ public async Task<T> GetEventAsync<T>(string eventId) {
return await _httpClient.GetFromJsonAsync<T>($"/_matrix/client/v3/rooms/{RoomId}/event/{eventId}");
}
@@ -246,9 +235,38 @@ public class GenericRoom {
$"/_matrix/client/v3/rooms/{RoomId}/redact/{eventToRedact}/{Guid.NewGuid()}", data)).Content.ReadFromJsonAsync<EventIdResponse>())!;
}
- public async Task InviteUser(string userId, string? reason = null) {
+ public async Task InviteUserAsync(string userId, string? reason = null) {
await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/invite", new UserIdAndReason(userId, reason));
}
+
+#region Disband room
+
+ public async Task DisbandRoomAsync() {
+ var states = GetFullStateAsync();
+ List<string> stateTypeIgnore = new() {
+ "m.room.create",
+ "m.room.power_levels",
+ "m.room.join_rules",
+ "m.room.history_visibility",
+ "m.room.guest_access",
+ "m.room.member",
+ };
+ await foreach (var state in states) {
+ if (state is null || state.RawContent is not { Count: > 0 }) continue;
+ if (state.Type == "m.room.member" && state.StateKey != Homeserver.UserId)
+ try {
+ await BanAsync(state.StateKey, "Disbanding room");
+ }
+ catch (MatrixException e) {
+ if (e.ErrorCode != "M_FORBIDDEN") throw;
+ }
+
+ if (stateTypeIgnore.Contains(state.Type)) continue;
+ await SendStateEventAsync(state.Type, state.StateKey, new());
+ }
+ }
+
+#endregion
}
public class RoomIdResponse {
diff --git a/LibMatrix/RoomTypes/SpaceRoom.cs b/LibMatrix/RoomTypes/SpaceRoom.cs
index a43ae82..4a8e247 100644
--- a/LibMatrix/RoomTypes/SpaceRoom.cs
+++ b/LibMatrix/RoomTypes/SpaceRoom.cs
@@ -1,26 +1,37 @@
using ArcaneLibs.Extensions;
using LibMatrix.Homeservers;
+using Microsoft.Extensions.Logging;
namespace LibMatrix.RoomTypes;
-public class SpaceRoom : GenericRoom {
- private new readonly AuthenticatedHomeserverGeneric _homeserver;
+public class SpaceRoom(AuthenticatedHomeserverGeneric homeserver, string roomId) : GenericRoom(homeserver, roomId) {
private readonly GenericRoom _room;
- public SpaceRoom(AuthenticatedHomeserverGeneric homeserver, string roomId) : base(homeserver, roomId) {
- _homeserver = homeserver;
- }
-
- private static SemaphoreSlim _semaphore = new(1, 1);
public async IAsyncEnumerable<GenericRoom> GetChildrenAsync(bool includeRemoved = false) {
- // await _semaphore.WaitAsync();
var rooms = new List<GenericRoom>();
var state = GetFullStateAsync();
await foreach (var stateEvent in state) {
- if (stateEvent.Type != "m.space.child") continue;
- if (stateEvent.RawContent.ToJson() != "{}" || includeRemoved)
- yield return _homeserver.GetRoom(stateEvent.StateKey);
+ if (stateEvent!.Type != "m.space.child") continue;
+ if (stateEvent.RawContent!.ToJson() != "{}" || includeRemoved)
+ yield return Homeserver.GetRoom(stateEvent.StateKey);
+ }
+ }
+
+ public async Task<EventIdResponse> AddChildAsync(GenericRoom room) {
+ var members = room.GetMembersAsync(true);
+ Dictionary<string, int> memberCountByHs = new();
+ await foreach (var member in members) {
+ var server = member.StateKey.Split(':')[1];
+ if (memberCountByHs.ContainsKey(server)) memberCountByHs[server]++;
+ else memberCountByHs[server] = 1;
}
- // _semaphore.Release();
+
+ var resp = await SendStateEventAsync("m.space.child", room.RoomId, new {
+ via = memberCountByHs
+ .OrderByDescending(x => x.Value)
+ .Select(x => x.Key)
+ .Take(10)
+ });
+ return resp;
}
}
diff --git a/LibMatrix/Services/HomeserverProviderService.cs b/LibMatrix/Services/HomeserverProviderService.cs
index 49167fa..666d2a2 100644
--- a/LibMatrix/Services/HomeserverProviderService.cs
+++ b/LibMatrix/Services/HomeserverProviderService.cs
@@ -9,29 +9,29 @@ using Microsoft.Extensions.Logging;
namespace LibMatrix.Services;
public class HomeserverProviderService {
- private readonly TieredStorageService _tieredStorageService;
private readonly ILogger<HomeserverProviderService> _logger;
private readonly HomeserverResolverService _homeserverResolverService;
- public HomeserverProviderService(TieredStorageService tieredStorageService,
- ILogger<HomeserverProviderService> logger, HomeserverResolverService homeserverResolverService) {
- _tieredStorageService = tieredStorageService;
+ public HomeserverProviderService(ILogger<HomeserverProviderService> logger, HomeserverResolverService homeserverResolverService) {
_logger = logger;
_homeserverResolverService = homeserverResolverService;
- logger.LogDebug("New HomeserverProviderService created with TieredStorageService<{}>!",
- string.Join(", ", tieredStorageService.GetType().GetProperties().Select(x => x.Name)));
}
private static Dictionary<string, SemaphoreSlim> _authenticatedHomeserverSemaphore = new();
private static Dictionary<string, AuthenticatedHomeserverGeneric> _authenticatedHomeServerCache = new();
+ private static Dictionary<string, SemaphoreSlim> _remoteHomeserverSemaphore = new();
+ private static Dictionary<string, RemoteHomeServer> _remoteHomeServerCache = new();
+
public async Task<AuthenticatedHomeserverGeneric> GetAuthenticatedWithToken(string homeserver, string accessToken,
string? proxy = null) {
var sem = _authenticatedHomeserverSemaphore.GetOrCreate(homeserver + accessToken, _ => new SemaphoreSlim(1, 1));
await sem.WaitAsync();
- if (_authenticatedHomeServerCache.ContainsKey(homeserver + accessToken)) {
- sem.Release();
- return _authenticatedHomeServerCache[homeserver + accessToken];
+ lock (_authenticatedHomeServerCache) {
+ if (_authenticatedHomeServerCache.ContainsKey(homeserver + accessToken)) {
+ sem.Release();
+ return _authenticatedHomeServerCache[homeserver + accessToken];
+ }
}
var domain = proxy ?? await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver);
@@ -45,12 +45,11 @@ public class HomeserverProviderService {
hs = new AuthenticatedHomeserverGeneric(homeserver, accessToken);
}
- hs.FullHomeServerDomain = domain;
hs._httpClient = hc;
hs._httpClient.Timeout = TimeSpan.FromMinutes(15);
hs._httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
- hs.WhoAmI = (await hs._httpClient.GetFromJsonAsync<WhoAmIResponse>("/_matrix/client/v3/account/whoami"))!;
+ // (() => hs.WhoAmI) = (await hs._httpClient.GetFromJsonAsync<WhoAmIResponse>("/_matrix/client/v3/account/whoami"))!;
lock(_authenticatedHomeServerCache)
_authenticatedHomeServerCache[homeserver + accessToken] = hs;
@@ -60,11 +59,10 @@ public class HomeserverProviderService {
}
public async Task<RemoteHomeServer> GetRemoteHomeserver(string homeserver, string? proxy = null) {
- var hs = new RemoteHomeServer(homeserver);
- hs.FullHomeServerDomain = proxy ?? await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver);
- hs._httpClient.Dispose();
- hs._httpClient = new MatrixHttpClient { BaseAddress = new Uri(hs.FullHomeServerDomain) };
- hs._httpClient.Timeout = TimeSpan.FromSeconds(120);
+ var hs = new RemoteHomeServer(proxy ?? await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver));
+ // hs._httpClient.Dispose();
+ // hs._httpClient = new MatrixHttpClient { BaseAddress = new Uri(hs.FullHomeServerDomain) };
+ // hs._httpClient.Timeout = TimeSpan.FromSeconds(120);
return hs;
}
diff --git a/LibMatrix/StateEvent.cs b/LibMatrix/StateEvent.cs
index 97348a5..b42bd64 100644
--- a/LibMatrix/StateEvent.cs
+++ b/LibMatrix/StateEvent.cs
@@ -2,6 +2,7 @@ using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
using ArcaneLibs;
using ArcaneLibs.Extensions;
using LibMatrix.EventTypes;
@@ -48,7 +49,7 @@ public class StateEvent {
return null;
}
- set => RawContent = JsonSerializer.Deserialize<JsonObject>(JsonSerializer.Serialize(value));
+ set => RawContent = JsonSerializer.Deserialize<JsonObject>(JsonSerializer.Serialize(value, value.GetType()));
}
[JsonPropertyName("state_key")]
@@ -120,3 +121,32 @@ public class StateEvent {
[JsonIgnore]
public string cdtype => TypedContent.GetType().Name;
}
+
+/*
+public class StateEventContentPolymorphicTypeInfoResolver : DefaultJsonTypeInfoResolver
+{
+ public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
+ {
+ JsonTypeInfo jsonTypeInfo = base.GetTypeInfo(type, options);
+
+ Type baseType = typeof(EventContent);
+ if (jsonTypeInfo.Type == baseType) {
+ jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions {
+ TypeDiscriminatorPropertyName = "type",
+ IgnoreUnrecognizedTypeDiscriminators = true,
+ UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType,
+
+ DerivedTypes = StateEvent.KnownStateEventTypesByName.Select(x => new JsonDerivedType(x.Value, x.Key)).ToList()
+
+ // DerivedTypes = new ClassCollector<EventContent>()
+ // .ResolveFromAllAccessibleAssemblies()
+ // .SelectMany(t => t.GetCustomAttributes<MatrixEventAttribute>()
+ // .Select(a => new JsonDerivedType(t, attr.EventName));
+
+ };
+ }
+
+ return jsonTypeInfo;
+ }
+}
+*/
diff --git a/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs b/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs
new file mode 100644
index 0000000..abd3e99
--- /dev/null
+++ b/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs
@@ -0,0 +1,73 @@
+using ArcaneLibs.Extensions;
+using LibMatrix.Homeservers;
+using LibMatrix.Responses;
+
+namespace LibMatrix.Tests.Abstractions;
+
+public static class HomeserverAbstraction {
+ public static async Task<AuthenticatedHomeserverGeneric> GetHomeserver() {
+ var rhs = new RemoteHomeServer("https://matrixunittests.rory.gay");
+ // string username = Guid.NewGuid().ToString();
+ // string password = Guid.NewGuid().ToString();
+ string username = "@f1a2d2d6-1924-421b-91d0-893b347b2a49:matrixunittests.rory.gay";
+ string password = "d6d782d6-8bc9-4fac-9cd8-78e101b4298b";
+ LoginResponse reg;
+ try {
+ reg = await rhs.LoginAsync(username, password);
+ }
+ catch (MatrixException e) {
+ if (e.ErrorCode == "M_FORBIDDEN") {
+ await rhs.RegisterAsync(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "Unit tests!");
+ reg = await rhs.RegisterAsync(username, password, "Unit tests!");
+ }
+ else throw new Exception("Failed to register", e);
+ }
+
+ var hs = await reg.GetAuthenticatedHomeserver("https://matrixunittests.rory.gay");
+
+ var rooms = await hs.GetJoinedRooms();
+
+ var disbandRoomTasks = rooms.Select(async room => {
+ // await room.DisbandRoomAsync();
+ await room.LeaveAsync();
+ await room.ForgetAsync();
+ return room;
+ }).ToList();
+ await Task.WhenAll(disbandRoomTasks);
+
+ // foreach (var room in rooms) {
+ // // await room.DisbandRoomAsync();
+ // await room.LeaveAsync();
+ // await room.ForgetAsync();
+ // }
+
+ return hs;
+ }
+
+ public static async Task<AuthenticatedHomeserverGeneric> GetRandomHomeserver() {
+ var rhs = new RemoteHomeServer("https://matrixunittests.rory.gay");
+ LoginResponse reg = await rhs.RegisterAsync(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "Unit tests!");
+ var hs = await reg.GetAuthenticatedHomeserver("https://matrixunittests.rory.gay");
+
+ var rooms = await hs.GetJoinedRooms();
+
+ var disbandRoomTasks = rooms.Select(async room => {
+ // await room.DisbandRoomAsync();
+ await room.LeaveAsync();
+ await room.ForgetAsync();
+ return room;
+ }).ToList();
+ await Task.WhenAll(disbandRoomTasks);
+
+ return hs;
+ }
+
+ public static async IAsyncEnumerable<AuthenticatedHomeserverGeneric> GetRandomHomeservers(int count = 1) {
+ var createSpaceTasks = Enumerable
+ .Range(0, count)
+ .Select(_ => GetRandomHomeserver()).ToAsyncEnumerable();
+ await foreach (var hs in createSpaceTasks) {
+ yield return hs;
+ }
+ }
+}
diff --git a/Tests/LibMatrix.Tests/Abstractions/RoomAbstraction.cs b/Tests/LibMatrix.Tests/Abstractions/RoomAbstraction.cs
new file mode 100644
index 0000000..44c35da
--- /dev/null
+++ b/Tests/LibMatrix.Tests/Abstractions/RoomAbstraction.cs
@@ -0,0 +1,76 @@
+using ArcaneLibs.Extensions;
+using LibMatrix.EventTypes.Spec.State;
+using LibMatrix.Homeservers;
+using LibMatrix.Responses;
+using LibMatrix.RoomTypes;
+
+namespace LibMatrix.Tests.Abstractions;
+
+public static class RoomAbstraction {
+ public static async Task<GenericRoom> GetTestRoom(AuthenticatedHomeserverGeneric hs) {
+ var testRoom = await hs.CreateRoom(new CreateRoomRequest() {
+ Name = "LibMatrix Test Room",
+ // Visibility = CreateRoomVisibility.Public,
+ RoomAliasName = Guid.NewGuid().ToString()
+ });
+
+ await testRoom.SendStateEventAsync("gay.rory.libmatrix.unit_test_room", new());
+
+ return testRoom;
+ }
+
+ private static SemaphoreSlim _spaceSemaphore = null!;
+
+ public static async Task<SpaceRoom> GetTestSpace(AuthenticatedHomeserverGeneric hs, int roomCount = 100, bool addSpaces = false, int spaceSizeReduction = 10) {
+ _spaceSemaphore ??= new(roomCount / spaceSizeReduction, roomCount / spaceSizeReduction);
+ var crq = new CreateRoomRequest() {
+ Name = $"LibMatrix Test Space ({roomCount} children)",
+ // Visibility = CreateRoomVisibility.Public,
+ RoomAliasName = Guid.NewGuid().ToString(),
+ InitialState = new()
+ };
+ crq._creationContentBaseType.Type = "m.space";
+
+
+ var createRoomTasks = Enumerable.Range(0, roomCount)
+ .Select(_ => hs.CreateRoom(new CreateRoomRequest() {
+ Name = $"LibMatrix Test Room {Guid.NewGuid()}",
+ // Visibility = CreateRoomVisibility.Public,
+ RoomAliasName = Guid.NewGuid().ToString()
+ })).ToAsyncEnumerable();
+
+ await foreach (var room in createRoomTasks) {
+ crq.InitialState.Add(new() {
+ Type = "m.space.child",
+ StateKey = room.RoomId,
+ TypedContent = new SpaceChildEventContent() {
+ Via = new() {
+ room.RoomId.Split(":")[1]
+ }
+ }
+ });
+ }
+
+ if (addSpaces) {
+ for (int i = 0; i < roomCount; i++) {
+ var space = await GetTestSpace(hs, roomCount - spaceSizeReduction, true, spaceSizeReduction);
+ crq.InitialState.Add(new() {
+ Type = "m.space.child",
+ StateKey = space.RoomId,
+ TypedContent = new SpaceChildEventContent() {
+ Via = new() {
+ space.RoomId.Split(":")[1]
+ }
+ }
+ });
+ }
+ }
+
+ var testSpace = (await hs.CreateRoom(crq)).AsSpace;
+
+ await testSpace.SendStateEventAsync("gay.rory.libmatrix.unit_test_room", new());
+
+ // _spaceSemaphore.Release();
+ return testSpace;
+ }
+}
diff --git a/Tests/LibMatrix.Tests/DataTests/WhoAmITests.cs b/Tests/LibMatrix.Tests/DataTests/WhoAmITests.cs
new file mode 100644
index 0000000..f737363
--- /dev/null
+++ b/Tests/LibMatrix.Tests/DataTests/WhoAmITests.cs
@@ -0,0 +1,10 @@
+namespace LibMatrix.Tests.DataTests;
+
+public static class WhoAmITests {
+ public static void VerifyRequiredFields(this WhoAmIResponse obj, bool isAppservice = false) {
+ Assert.NotNull(obj);
+ Assert.NotNull(obj.UserId);
+ if(!isAppservice)
+ Assert.NotNull(obj.DeviceId);
+ }
+}
diff --git a/Tests/LibMatrix.Tests/Tests/AuthTests.cs b/Tests/LibMatrix.Tests/Tests/AuthTests.cs
index 72a509d..5476b84 100644
--- a/Tests/LibMatrix.Tests/Tests/AuthTests.cs
+++ b/Tests/LibMatrix.Tests/Tests/AuthTests.cs
@@ -1,4 +1,5 @@
using LibMatrix.Services;
+using LibMatrix.Tests.DataTests;
using LibMatrix.Tests.Fixtures;
using Xunit.Abstractions;
using Xunit.Microsoft.DependencyInjection.Abstracts;
@@ -45,6 +46,24 @@ public class AuthTests : TestBed<TestFixture> {
var hs = await _provider.GetAuthenticatedWithToken(_config.TestHomeserver!, login.AccessToken);
Assert.NotNull(hs);
Assert.NotNull(hs.WhoAmI);
+ hs.WhoAmI.VerifyRequiredFields();
+ Assert.NotNull(hs.UserId);
+ Assert.NotNull(hs.AccessToken);
+ await hs.Logout();
+ }
+
+ [Fact]
+ public async Task RegisterAsync() {
+ var rhs = await _provider.GetRemoteHomeserver("matrixunittests.rory.gay");
+ var reg = await rhs.RegisterAsync(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "Unit tests!");
+ Assert.NotNull(reg);
+ Assert.NotNull(reg.AccessToken);
+ Assert.NotNull(reg.DeviceId);
+ Assert.NotNull(reg.UserId);
+ var hs = await reg.GetAuthenticatedHomeserver();
+ Assert.NotNull(hs);
+ Assert.NotNull(hs.WhoAmI);
+ hs.WhoAmI.VerifyRequiredFields();
Assert.NotNull(hs.UserId);
Assert.NotNull(hs.AccessToken);
await hs.Logout();
diff --git a/Tests/LibMatrix.Tests/Tests/RoomTests.cs b/Tests/LibMatrix.Tests/Tests/RoomTests.cs
index ef63ec9..17c219d 100644
--- a/Tests/LibMatrix.Tests/Tests/RoomTests.cs
+++ b/Tests/LibMatrix.Tests/Tests/RoomTests.cs
@@ -1,7 +1,9 @@
+using System.Text;
using ArcaneLibs.Extensions;
using LibMatrix.EventTypes.Spec.State;
using LibMatrix.Homeservers;
using LibMatrix.Services;
+using LibMatrix.Tests.Abstractions;
using LibMatrix.Tests.Fixtures;
using Xunit.Abstractions;
using Xunit.Microsoft.DependencyInjection.Abstracts;
@@ -22,34 +24,29 @@ public class RoomTests : TestBed<TestFixture> {
}
private async Task<AuthenticatedHomeserverGeneric> GetHomeserver() {
- Assert.False(string.IsNullOrWhiteSpace(_config.TestHomeserver), $"{nameof(_config.TestHomeserver)} must be set in appsettings!");
- Assert.False(string.IsNullOrWhiteSpace(_config.TestUsername), $"{nameof(_config.TestUsername)} must be set in appsettings!");
- Assert.False(string.IsNullOrWhiteSpace(_config.TestPassword), $"{nameof(_config.TestPassword)} must be set in appsettings!");
-
- // var server = await _resolver.ResolveHomeserverFromWellKnown(_config.TestHomeserver!);
- var login = await _provider.Login(_config.TestHomeserver!, _config.TestUsername!, _config.TestPassword!);
- Assert.NotNull(login);
-
- var hs = await _provider.GetAuthenticatedWithToken(_config.TestHomeserver!, login.AccessToken);
- return hs;
+ return await HomeserverAbstraction.GetHomeserver();
}
[Fact]
public async Task GetJoinedRoomsAsync() {
- var hs = await GetHomeserver();
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ //make 100 rooms
+ var createRoomTasks = Enumerable.Range(0, 100).Select(_ => RoomAbstraction.GetTestRoom(hs)).ToList();
+ await Task.WhenAll(createRoomTasks);
var rooms = await hs.GetJoinedRooms();
Assert.NotNull(rooms);
Assert.NotEmpty(rooms);
Assert.All(rooms, Assert.NotNull);
+ Assert.Equal(100, rooms.Count);
await hs.Logout();
}
[Fact]
public async Task GetNameAsync() {
- var hs = await GetHomeserver();
- var room = hs.GetRoom(_config.TestRoomId);
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var room = await RoomAbstraction.GetTestRoom(hs);
Assert.NotNull(room);
var name = await room.GetNameAsync();
Assert.NotNull(name);
@@ -58,8 +55,8 @@ public class RoomTests : TestBed<TestFixture> {
[SkippableFact(typeof(MatrixException))]
public async Task GetTopicAsync() {
- var hs = await GetHomeserver();
- var room = hs.GetRoom(_config.TestRoomId);
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var room = await RoomAbstraction.GetTestRoom(hs);
Assert.NotNull(room);
var topic = await room.GetTopicAsync();
Assert.NotNull(topic);
@@ -72,8 +69,8 @@ public class RoomTests : TestBed<TestFixture> {
Assert.True(StateEvent.KnownStateEventTypes is { Count: > 0 }, "StateEvent.KnownStateEventTypes is empty!");
Assert.True(StateEvent.KnownStateEventTypesByName is { Count: > 0 }, "StateEvent.KnownStateEventTypesByName is empty!");
- var hs = await GetHomeserver();
- var room = hs.GetRoom(_config.TestRoomId);
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var room = await RoomAbstraction.GetTestRoom(hs);
Assert.NotNull(room);
var members = room.GetMembersAsync();
Assert.NotNull(members);
@@ -98,41 +95,10 @@ public class RoomTests : TestBed<TestFixture> {
Assert.True(hitMembers, "No members were found in the room");
}
- /*
- tests remaining:
- GetStateAsync(string,string) 0% 8/8
- GetMessagesAsync(string,int,string,string) 0% 7/7
- JoinAsync(string[],string) 0% 8/8
- SendMessageEventAsync(RoomMessageEventContent) 0% 1/1
- GetAliasesAsync() 0% 4/4
- GetCanonicalAliasAsync() 0% 1/1
- GetAvatarUrlAsync() 0% 1/1
- GetJoinRuleAsync() 0% 1/1
- GetHistoryVisibilityAsync() 0% 1/1
- GetGuestAccessAsync() 0% 1/1
- GetCreateEventAsync() 0% 1/1
- GetRoomType() 0% 4/4
- GetPowerLevelsAsync() 0% 1/1
- ForgetAsync() 0% 1/1
- LeaveAsync(string) 0% 1/1
- KickAsync(string,string) 0% 1/1
- BanAsync(string,string) 0% 1/1
- UnbanAsync(string) 0% 1/1
- SendStateEventAsync(string,object) 0% 1/1
- SendStateEventAsync(string,string,object) 0% 1/1
- SendTimelineEventAsync(string,EventContent) 0% 5/5
- SendFileAsync(string,string,Stream) 0% 6/6
- GetRoomAccountData<T>(string) 0% 8/8
- SetRoomAccountData(string,object) 0% 7/7
- GetEvent<T>(string) 0% 3/3
- RedactEventAsync(string,string) 0% 4/4
- InviteUser(string,string) 0% 3/3
- */
-
[Fact]
public async Task JoinAsync() {
- var hs = await GetHomeserver();
- var room = hs.GetRoom(_config.TestRoomId);
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var room = await RoomAbstraction.GetTestRoom(hs);
Assert.NotNull(room);
var id = await room.JoinAsync();
Assert.NotNull(id);
@@ -142,8 +108,8 @@ public class RoomTests : TestBed<TestFixture> {
[SkippableFact(typeof(MatrixException))]
public async Task GetAliasesAsync() {
- var hs = await GetHomeserver();
- var room = hs.GetRoom(_config.TestRoomId);
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var room = await RoomAbstraction.GetTestRoom(hs);
Assert.NotNull(room);
var aliases = await room.GetAliasesAsync();
Assert.NotNull(aliases);
@@ -153,8 +119,8 @@ public class RoomTests : TestBed<TestFixture> {
[SkippableFact(typeof(MatrixException))]
public async Task GetCanonicalAliasAsync() {
- var hs = await GetHomeserver();
- var room = hs.GetRoom(_config.TestRoomId);
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var room = await RoomAbstraction.GetTestRoom(hs);
Assert.NotNull(room);
var alias = await room.GetCanonicalAliasAsync();
Assert.NotNull(alias);
@@ -164,8 +130,8 @@ public class RoomTests : TestBed<TestFixture> {
[SkippableFact(typeof(MatrixException))]
public async Task GetAvatarUrlAsync() {
- var hs = await GetHomeserver();
- var room = hs.GetRoom(_config.TestRoomId);
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var room = await RoomAbstraction.GetTestRoom(hs);
Assert.NotNull(room);
var url = await room.GetAvatarUrlAsync();
Assert.NotNull(url);
@@ -175,8 +141,8 @@ public class RoomTests : TestBed<TestFixture> {
[Fact]
public async Task GetJoinRuleAsync() {
- var hs = await GetHomeserver();
- var room = hs.GetRoom(_config.TestRoomId);
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var room = await RoomAbstraction.GetTestRoom(hs);
Assert.NotNull(room);
var rule = await room.GetJoinRuleAsync();
Assert.NotNull(rule);
@@ -186,8 +152,8 @@ public class RoomTests : TestBed<TestFixture> {
[Fact]
public async Task GetHistoryVisibilityAsync() {
- var hs = await GetHomeserver();
- var room = hs.GetRoom(_config.TestRoomId);
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var room = await RoomAbstraction.GetTestRoom(hs);
Assert.NotNull(room);
var visibility = await room.GetHistoryVisibilityAsync();
Assert.NotNull(visibility);
@@ -197,8 +163,8 @@ public class RoomTests : TestBed<TestFixture> {
[Fact]
public async Task GetGuestAccessAsync() {
- var hs = await GetHomeserver();
- var room = hs.GetRoom(_config.TestRoomId);
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var room = await RoomAbstraction.GetTestRoom(hs);
Assert.NotNull(room);
try {
var access = await room.GetGuestAccessAsync();
@@ -207,15 +173,15 @@ public class RoomTests : TestBed<TestFixture> {
Assert.NotEmpty(access.GuestAccess);
}
catch (Exception e) {
- if(e is not MatrixException exception) throw;
+ if (e is not MatrixException exception) throw;
Assert.Equal("M_NOT_FOUND", exception.ErrorCode);
}
}
[Fact]
public async Task GetCreateEventAsync() {
- var hs = await GetHomeserver();
- var room = hs.GetRoom(_config.TestRoomId);
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var room = await RoomAbstraction.GetTestRoom(hs);
Assert.NotNull(room);
var create = await room.GetCreateEventAsync();
Assert.NotNull(create);
@@ -225,16 +191,16 @@ public class RoomTests : TestBed<TestFixture> {
[Fact]
public async Task GetRoomType() {
- var hs = await GetHomeserver();
- var room = hs.GetRoom(_config.TestRoomId);
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var room = await RoomAbstraction.GetTestRoom(hs);
Assert.NotNull(room);
await room.GetRoomType();
}
[Fact]
public async Task GetPowerLevelsAsync() {
- var hs = await GetHomeserver();
- var room = hs.GetRoom(_config.TestRoomId);
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var room = await RoomAbstraction.GetTestRoom(hs);
Assert.NotNull(room);
var power = await room.GetPowerLevelsAsync();
Assert.NotNull(power);
@@ -246,54 +212,73 @@ public class RoomTests : TestBed<TestFixture> {
Assert.NotNull(power.EventsDefault);
Assert.NotNull(power.UsersDefault);
Assert.NotNull(power.Users);
- Assert.NotNull(power.Events);
+ // Assert.NotNull(power.Events);
}
- [Fact(Skip = "This test is destructive!")]
+ [Fact]
public async Task ForgetAsync() {
- var hs = await GetHomeserver();
- var room = hs.GetRoom(_config.TestRoomId);
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var room = await RoomAbstraction.GetTestRoom(hs);
Assert.NotNull(room);
await room.ForgetAsync();
}
- [Fact(Skip = "This test is destructive!")]
+ [Fact]
public async Task LeaveAsync() {
- var hs = await GetHomeserver();
- var room = hs.GetRoom(_config.TestRoomId);
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var room = await RoomAbstraction.GetTestRoom(hs);
Assert.NotNull(room);
await room.LeaveAsync();
}
- [Fact(Skip = "This test is destructive!")]
+ [Fact]
public async Task KickAsync() {
- var hs = await GetHomeserver();
- var room = hs.GetRoom(_config.TestRoomId);
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var hs2 = await HomeserverAbstraction.GetRandomHomeserver();
+ var room = await RoomAbstraction.GetTestRoom(hs);
Assert.NotNull(room);
- // await room.KickAsync(_config.TestUserId, "test");
+ await room.InviteUserAsync(hs2.UserId,"Unit test!");
+ await hs2.GetRoom(room.RoomId).JoinAsync();
+ await room.KickAsync(hs2.UserId, "test");
+ var banState = await room.GetStateAsync<RoomMemberEventContent>("m.room.member", hs2.UserId);
+ Assert.NotNull(banState);
+ Assert.Equal("leave", banState.Membership);
}
- [Fact(Skip = "This test is destructive!")]
+ [Fact]
public async Task BanAsync() {
- var hs = await GetHomeserver();
- var room = hs.GetRoom(_config.TestRoomId);
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var hs2 = await HomeserverAbstraction.GetRandomHomeserver();
+ var room = await RoomAbstraction.GetTestRoom(hs);
Assert.NotNull(room);
- // await room.BanAsync(_config.TestUserId, "test");
+ await room.BanAsync(hs2.UserId, "test");
+ var banState = await room.GetStateAsync<RoomMemberEventContent>("m.room.member", hs2.UserId);
+ Assert.NotNull(banState);
+ Assert.Equal("ban", banState.Membership);
}
- [Fact(Skip = "This test is destructive!")]
+ [Fact]
public async Task UnbanAsync() {
- var hs = await GetHomeserver();
- var room = hs.GetRoom(_config.TestRoomId);
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var hs2 = await HomeserverAbstraction.GetRandomHomeserver();
+ var room = await RoomAbstraction.GetTestRoom(hs);
Assert.NotNull(room);
- // await room.UnbanAsync(_config.TestUserId);
+ await room.BanAsync(hs2.UserId, "test");
+ var banState = await room.GetStateAsync<RoomMemberEventContent>("m.room.member", hs2.UserId);
+ Assert.NotNull(banState);
+ Assert.Equal("ban", banState.Membership);
+ await room.UnbanAsync(hs2.UserId);
+ var unbanState = await room.GetStateAsync<RoomMemberEventContent>("m.room.member", hs2.UserId);
+ Assert.NotNull(unbanState);
+ Assert.Equal("leave", unbanState.Membership);
}
[SkippableFact(typeof(MatrixException))]
public async Task SendStateEventAsync() {
- var hs = await GetHomeserver();
- var room = hs.GetRoom(_config.TestRoomId);
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var room = await RoomAbstraction.GetTestRoom(hs);
Assert.NotNull(room);
+
await room.SendStateEventAsync("gay.rory.libmatrix.unit_tests", new ProfileResponseEventContent() {
DisplayName = "wee_woo",
AvatarUrl = "no"
@@ -305,12 +290,21 @@ public class RoomTests : TestBed<TestFixture> {
}
[SkippableFact(typeof(MatrixException))]
- public async Task GetStateEventAsync() {
+ public async Task SendAndGetStateEventAsync() {
await SendStateEventAsync();
-
- var hs = await GetHomeserver();
- var room = hs.GetRoom(_config.TestRoomId);
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var room = await RoomAbstraction.GetTestRoom(hs);
Assert.NotNull(room);
+
+ await room.SendStateEventAsync("gay.rory.libmatrix.unit_tests", new ProfileResponseEventContent() {
+ DisplayName = "wee_woo",
+ AvatarUrl = "no"
+ });
+ await room.SendStateEventAsync("gay.rory.libmatrix.unit_tests", "state_key_maybe", new ProfileResponseEventContent() {
+ DisplayName = "wee_woo",
+ AvatarUrl = "yes"
+ });
+
var state1 = await room.GetStateAsync<ProfileResponseEventContent>("gay.rory.libmatrix.unit_tests");
Assert.NotNull(state1);
Assert.NotNull(state1.DisplayName);
@@ -329,4 +323,62 @@ public class RoomTests : TestBed<TestFixture> {
Assert.Equal("wee_woo", state2.DisplayName);
Assert.Equal("yes", state2.AvatarUrl);
}
+
+ [Fact]
+ public async Task DisbandAsync() {
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var room = await RoomAbstraction.GetTestRoom(hs);
+ Assert.NotNull(room);
+
+ await room.DisbandRoomAsync();
+ }
+
+ [Fact]
+ public async Task SendFileAsync() {
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var room = await RoomAbstraction.GetTestRoom(hs);
+ Assert.NotNull(room);
+
+ var res = await room.SendFileAsync("test.txt", new MemoryStream(Encoding.UTF8.GetBytes("This test was written by Emma [it/its], member of the Rory& system." +
+ "\nIf you are reading this on matrix, it means the unit test for uploading a file works!")));
+ Assert.NotNull(res);
+ Assert.NotNull(res.EventId);
+ }
+
+ [Fact]
+ public async Task GetSpaceChildrenAsync() {
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var space = await RoomAbstraction.GetTestSpace(hs, 2, false, 1);
+ Assert.NotNull(space);
+ var children = space.GetChildrenAsync();
+ Assert.NotNull(children);
+ int found = 0;
+ await foreach (var room in children) {
+ found++;
+ }
+ Assert.Equal(2, found);
+ }
+
+ [Fact]
+ public async Task InviteAndJoinAsync() {
+ var otherUsers = HomeserverAbstraction.GetRandomHomeservers(7);
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var room = await RoomAbstraction.GetTestRoom(hs);
+ Assert.NotNull(room);
+
+ // var expectedCount = 1;
+
+ await foreach(var otherUser in otherUsers) {
+ await room.InviteUserAsync(otherUser.UserId);
+ await otherUser.GetRoom(room.RoomId).JoinAsync();
+ }
+
+ var states = room.GetMembersAsync(false);
+ var count = 0;
+ await foreach(var state in states) {
+ count++;
+ }
+ // Assert.Equal(++expectedCount, count);
+ Assert.Equal(8, count);
+ }
}
|