diff options
76 files changed, 927 insertions, 212 deletions
diff --git a/.gitignore b/.gitignore index 625aad4..fba66db 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ MatrixRoomUtils.Bot/bot_data/ appsettings.Local*.json appservice.yaml appservice.json +Tests/LibMatrix.Tests/appsettings.json diff --git a/ExampleBots/LibMatrix.ExampleBot/Bot/Commands/CmdCommand.cs b/ExampleBots/LibMatrix.ExampleBot/Bot/Commands/CmdCommand.cs index 5b2828e..f3b4dde 100644 --- a/ExampleBots/LibMatrix.ExampleBot/Bot/Commands/CmdCommand.cs +++ b/ExampleBots/LibMatrix.ExampleBot/Bot/Commands/CmdCommand.cs @@ -1,6 +1,6 @@ using ArcaneLibs.Extensions; +using LibMatrix.EventTypes.Spec; using LibMatrix.ExampleBot.Bot.Interfaces; -using LibMatrix.StateEventTypes.Spec; namespace LibMatrix.ExampleBot.Bot.Commands; @@ -18,7 +18,7 @@ public class CmdCommand : ICommand { cmd = cmd.Trim(); cmd += "\""; - await ctx.Room.SendMessageEventAsync("m.room.message", new RoomMessageEventContent(body: $"Command being executed: `{cmd}`")); + await ctx.Room.SendMessageEventAsync(new RoomMessageEventContent(body: $"Command being executed: `{cmd}`")); var output = ArcaneLibs.Util.GetCommandOutputAsync( Environment.OSVersion.Platform == PlatformID.Unix ? "/bin/sh" : "cmd.exe", @@ -27,7 +27,7 @@ public class CmdCommand : ICommand { // .Split("\n").ToList(); var msg = ""; - EventIdResponse? msgId = await ctx.Room.SendMessageEventAsync("m.room.message", new RoomMessageEventContent { + EventIdResponse? msgId = await ctx.Room.SendMessageEventAsync(new RoomMessageEventContent { FormattedBody = $"Waiting for command output...", Body = msg.RemoveAnsi(), Format = "m.notice" @@ -38,14 +38,14 @@ public class CmdCommand : ICommand { Console.WriteLine($"{@out.Length:0000} {@out}"); msg += @out + "\n"; if (lastSendTask.IsCompleted) - lastSendTask = ctx.Room.SendMessageEventAsync("m.room.message", new RoomMessageEventContent { + lastSendTask = ctx.Room.SendMessageEventAsync(new RoomMessageEventContent { FormattedBody = $"<pre class=\"language-csharp\">\n{msg}\n</pre>", Body = msg.RemoveAnsi(), Format = "org.matrix.custom.html" }); if (msg.Length > 31000) { await lastSendTask; - msgId = await ctx.Room.SendMessageEventAsync("m.room.message", new RoomMessageEventContent { + msgId = await ctx.Room.SendMessageEventAsync(new RoomMessageEventContent { FormattedBody = $"Waiting for command output...", Body = msg.RemoveAnsi(), Format = "m.notice" diff --git a/ExampleBots/LibMatrix.ExampleBot/Bot/Commands/HelpCommand.cs b/ExampleBots/LibMatrix.ExampleBot/Bot/Commands/HelpCommand.cs index c750130..23c4fe2 100644 --- a/ExampleBots/LibMatrix.ExampleBot/Bot/Commands/HelpCommand.cs +++ b/ExampleBots/LibMatrix.ExampleBot/Bot/Commands/HelpCommand.cs @@ -1,6 +1,6 @@ using System.Text; +using LibMatrix.EventTypes.Spec; using LibMatrix.ExampleBot.Bot.Interfaces; -using LibMatrix.StateEventTypes.Spec; using Microsoft.Extensions.DependencyInjection; namespace LibMatrix.ExampleBot.Bot.Commands; @@ -17,6 +17,6 @@ public class HelpCommand(IServiceProvider services) : ICommand { sb.AppendLine($"- {command.Name}: {command.Description}"); } - await ctx.Room.SendMessageEventAsync("m.room.message", new RoomMessageEventContent(body: sb.ToString())); + await ctx.Room.SendMessageEventAsync(new RoomMessageEventContent(body: sb.ToString())); } } diff --git a/ExampleBots/LibMatrix.ExampleBot/Bot/Commands/PingCommand.cs b/ExampleBots/LibMatrix.ExampleBot/Bot/Commands/PingCommand.cs index a261a59..ba242fe 100644 --- a/ExampleBots/LibMatrix.ExampleBot/Bot/Commands/PingCommand.cs +++ b/ExampleBots/LibMatrix.ExampleBot/Bot/Commands/PingCommand.cs @@ -1,5 +1,5 @@ +using LibMatrix.EventTypes.Spec; using LibMatrix.ExampleBot.Bot.Interfaces; -using LibMatrix.StateEventTypes.Spec; namespace LibMatrix.ExampleBot.Bot.Commands; @@ -8,6 +8,6 @@ public class PingCommand : ICommand { public string Description { get; } = "Pong!"; public async Task Invoke(CommandContext ctx) { - await ctx.Room.SendMessageEventAsync("m.room.message", new RoomMessageEventContent(body: "pong!")); + await ctx.Room.SendMessageEventAsync(new RoomMessageEventContent(body: "pong!")); } } diff --git a/ExampleBots/LibMatrix.ExampleBot/Bot/Interfaces/CommandContext.cs b/ExampleBots/LibMatrix.ExampleBot/Bot/Interfaces/CommandContext.cs index 3715cb6..9b6ef7a 100644 --- a/ExampleBots/LibMatrix.ExampleBot/Bot/Interfaces/CommandContext.cs +++ b/ExampleBots/LibMatrix.ExampleBot/Bot/Interfaces/CommandContext.cs @@ -1,6 +1,6 @@ +using LibMatrix.EventTypes.Spec; using LibMatrix.Responses; using LibMatrix.RoomTypes; -using LibMatrix.StateEventTypes.Spec; namespace LibMatrix.ExampleBot.Bot.Interfaces; diff --git a/ExampleBots/LibMatrix.ExampleBot/Bot/MRUBot.cs b/ExampleBots/LibMatrix.ExampleBot/Bot/MRUBot.cs index 0b4e2ba..f04ec3a 100644 --- a/ExampleBots/LibMatrix.ExampleBot/Bot/MRUBot.cs +++ b/ExampleBots/LibMatrix.ExampleBot/Bot/MRUBot.cs @@ -1,10 +1,11 @@ using System.Diagnostics.CodeAnalysis; using ArcaneLibs.Extensions; +using LibMatrix.EventTypes.Spec; +using LibMatrix.EventTypes.Spec.State; using LibMatrix.ExampleBot.Bot.Interfaces; using LibMatrix.Extensions; using LibMatrix.Homeservers; using LibMatrix.Services; -using LibMatrix.StateEventTypes.Spec; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -43,7 +44,7 @@ public class MRUBot : IHostedService { throw; } - await (await hs.GetRoom("!DoHEdFablOLjddKWIp:rory.gay")).JoinAsync(); + await (hs.GetRoom("!DoHEdFablOLjddKWIp:rory.gay")).JoinAsync(); // foreach (var room in await hs.GetJoinedRooms()) { // if(room.RoomId is "!OGEhHVWSdvArJzumhm:matrix.org") continue; @@ -61,12 +62,12 @@ public class MRUBot : IHostedService { $"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") { try { - var senderProfile = await hs.GetProfile(inviteEvent.Sender); - await (await hs.GetRoom(args.Key)).JoinAsync(reason: $"I was invited by {senderProfile.DisplayName ?? inviteEvent.Sender}!"); + var senderProfile = await hs.GetProfileAsync(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 (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); } } }); @@ -74,13 +75,13 @@ public class MRUBot : IHostedService { _logger.LogInformation( "Got timeline event in {}: {}", @event.RoomId, @event.ToJson(indent: false, ignoreNull: true)); - var room = await hs.GetRoom(@event.RoomId); + var room = hs.GetRoom(@event.RoomId); // _logger.LogInformation(eventResponse.ToJson(indent: false)); if (@event is { Type: "m.room.message", TypedContent: RoomMessageEventContent message }) { if (message is { MessageType: "m.text" } && message.Body.StartsWith(_configuration.Prefix)) { var command = _commands.FirstOrDefault(x => x.Name == message.Body.Split(' ')[0][_configuration.Prefix.Length..]); if (command == null) { - await room.SendMessageEventAsync("m.room.message", + await room.SendMessageEventAsync( new RoomMessageEventContent(messageType: "m.text", body: "Command not found!")); return; } @@ -93,7 +94,7 @@ public class MRUBot : IHostedService { await command.Invoke(ctx); } else { - await room.SendMessageEventAsync("m.room.message", + await room.SendMessageEventAsync( new RoomMessageEventContent(messageType: "m.text", body: "You do not have permission to run this command!")); } } diff --git a/ExampleBots/LibMatrix.ExampleBot/Bot/StartupTasks/ServerRoomSizeCalulator.cs b/ExampleBots/LibMatrix.ExampleBot/Bot/StartupTasks/ServerRoomSizeCalulator.cs index 4785192..890db85 100644 --- a/ExampleBots/LibMatrix.ExampleBot/Bot/StartupTasks/ServerRoomSizeCalulator.cs +++ b/ExampleBots/LibMatrix.ExampleBot/Bot/StartupTasks/ServerRoomSizeCalulator.cs @@ -3,7 +3,6 @@ using ArcaneLibs.Extensions; using LibMatrix.ExampleBot.Bot.Interfaces; using LibMatrix.Homeservers; using LibMatrix.Services; -using LibMatrix.StateEventTypes.Spec; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -39,7 +38,7 @@ public class ServerRoomSizeCalulator : IHostedService { throw; } - await (await hs.GetRoom("!DoHEdFablOLjddKWIp:rory.gay")).JoinAsync(); + await (hs.GetRoom("!DoHEdFablOLjddKWIp:rory.gay")).JoinAsync(); Dictionary<string, int> totalRoomSize = new(); foreach (var room in await hs.GetJoinedRooms()) { diff --git a/ExampleBots/MediaModeratorPoC/Bot/Commands/BanMediaCommand.cs b/ExampleBots/MediaModeratorPoC/Bot/Commands/BanMediaCommand.cs index 4642007..d633f89 100644 --- a/ExampleBots/MediaModeratorPoC/Bot/Commands/BanMediaCommand.cs +++ b/ExampleBots/MediaModeratorPoC/Bot/Commands/BanMediaCommand.cs @@ -1,11 +1,11 @@ using System.Security.Cryptography; using ArcaneLibs.Extensions; +using LibMatrix.EventTypes.Spec; using LibMatrix.Helpers; using LibMatrix.Responses; using LibMatrix.Services; -using LibMatrix.StateEventTypes.Spec; +using LibMatrix.Utilities.Bot.Interfaces; using MediaModeratorPoC.Bot.AccountData; -using MediaModeratorPoC.Bot.Interfaces; using MediaModeratorPoC.Bot.StateEventTypes; namespace MediaModeratorPoC.Bot.Commands; @@ -17,11 +17,11 @@ public class BanMediaCommand(IServiceProvider services, HomeserverProviderServic public async Task<bool> CanInvoke(CommandContext ctx) { //check if user is admin in control room var botData = await ctx.Homeserver.GetAccountData<BotData>("gay.rory.media_moderator_poc_data"); - var controlRoom = await ctx.Homeserver.GetRoom(botData.ControlRoom); + var controlRoom = ctx.Homeserver.GetRoom(botData.ControlRoom); var isAdmin = (await controlRoom.GetPowerLevelsAsync())!.UserHasPermission(ctx.MessageEvent.Sender, "m.room.ban"); if (!isAdmin) { // await ctx.Reply("You do not have permission to use this command!"); - await (await ctx.Homeserver.GetRoom(botData.LogRoom!)).SendMessageEventAsync("m.room.message", + await ctx.Homeserver.GetRoom(botData.LogRoom!).SendMessageEventAsync( new RoomMessageEventContent(body: $"User {ctx.MessageEvent.Sender} tried to use command {Name} but does not have permission!", messageType: "m.text")); } @@ -30,14 +30,14 @@ public class BanMediaCommand(IServiceProvider services, HomeserverProviderServic public async Task Invoke(CommandContext ctx) { var botData = await ctx.Homeserver.GetAccountData<BotData>("gay.rory.media_moderator_poc_data"); - var policyRoom = await ctx.Homeserver.GetRoom(botData.PolicyRoom ?? botData.ControlRoom); - var logRoom = await ctx.Homeserver.GetRoom(botData.LogRoom ?? botData.ControlRoom); + var policyRoom = ctx.Homeserver.GetRoom(botData.PolicyRoom ?? botData.ControlRoom); + var logRoom = ctx.Homeserver.GetRoom(botData.LogRoom ?? botData.ControlRoom); //check if reply var messageContent = ctx.MessageEvent.TypedContent as RoomMessageEventContent; if (messageContent?.RelatesTo is { InReplyTo: not null }) { try { - await logRoom.SendMessageEventAsync("m.room.message", + await logRoom.SendMessageEventAsync( new RoomMessageEventContent( body: $"User {MessageFormatter.HtmlFormatMention(ctx.MessageEvent.Sender)} is trying to ban media {messageContent!.RelatesTo!.InReplyTo!.EventId}", messageType: "m.text")); @@ -47,14 +47,14 @@ public class BanMediaCommand(IServiceProvider services, HomeserverProviderServic //check if recommendation is in list if (ctx.Args.Length < 2) { - await ctx.Room.SendMessageEventAsync("m.room.message", MessageFormatter.FormatError("You must specify a recommendation type and reason!")); + await ctx.Room.SendMessageEventAsync(MessageFormatter.FormatError("You must specify a recommendation type and reason!")); return; } var recommendation = ctx.Args[0]; if (recommendation is not ("ban" or "kick" or "mute" or "redact" or "spoiler" or "warn" or "warn_admins")) { - await ctx.Room.SendMessageEventAsync("m.room.message", MessageFormatter.FormatError($"Invalid recommendation type {recommendation}, must be `warn_admins`, `warn`, `spoiler`, `redact`, `mute`, `kick` or `ban`!")); + await ctx.Room.SendMessageEventAsync(MessageFormatter.FormatError($"Invalid recommendation type {recommendation}, must be `warn_admins`, `warn`, `spoiler`, `redact`, `mute`, `kick` or `ban`!")); return; } @@ -69,7 +69,7 @@ public class BanMediaCommand(IServiceProvider services, HomeserverProviderServic fileHash = await hashAlgo.ComputeHashAsync(await ctx.Homeserver._httpClient.GetStreamAsync(resolvedUri)); } catch (Exception ex) { - await logRoom.SendMessageEventAsync("m.room.message", + await logRoom.SendMessageEventAsync( MessageFormatter.FormatException($"Error calculating file hash for {mxcUri} via {mxcUri.Split('/')[2]}, retrying via {ctx.Homeserver.HomeServerDomain}...", ex)); try { @@ -77,8 +77,8 @@ public class BanMediaCommand(IServiceProvider services, HomeserverProviderServic fileHash = await hashAlgo.ComputeHashAsync(await ctx.Homeserver._httpClient.GetStreamAsync(resolvedUri)); } catch (Exception ex2) { - await ctx.Room.SendMessageEventAsync("m.room.message", MessageFormatter.FormatException("Error calculating file hash", ex2)); - await logRoom.SendMessageEventAsync("m.room.message", + await ctx.Room.SendMessageEventAsync(MessageFormatter.FormatException("Error calculating file hash", ex2)); + await logRoom.SendMessageEventAsync( MessageFormatter.FormatException($"Error calculating file hash via {ctx.Homeserver.HomeServerDomain}!", ex2)); } } @@ -91,18 +91,18 @@ public class BanMediaCommand(IServiceProvider services, HomeserverProviderServic Recommendation = recommendation, }); - await ctx.Room.SendMessageEventAsync("m.room.message", MessageFormatter.FormatSuccessJson("Media policy created", policy)); - await logRoom.SendMessageEventAsync("m.room.message", MessageFormatter.FormatSuccessJson("Media policy created", policy)); + await ctx.Room.SendMessageEventAsync(MessageFormatter.FormatSuccessJson("Media policy created", policy)); + await logRoom.SendMessageEventAsync(MessageFormatter.FormatSuccessJson("Media policy created", policy)); } catch (Exception e) { - await logRoom.SendMessageEventAsync("m.room.message", MessageFormatter.FormatException("Error creating policy", e)); - await ctx.Room.SendMessageEventAsync("m.room.message", MessageFormatter.FormatException("Error creating policy", e)); + await logRoom.SendMessageEventAsync(MessageFormatter.FormatException("Error creating policy", e)); + await ctx.Room.SendMessageEventAsync(MessageFormatter.FormatException("Error creating policy", e)); await using var stream = new MemoryStream(e.ToString().AsBytes().ToArray()); await logRoom.SendFileAsync("m.file", "error.log.cs", stream); } } else { - await ctx.Room.SendMessageEventAsync("m.room.message", MessageFormatter.FormatError("This command must be used in reply to a message!")); + await ctx.Room.SendMessageEventAsync(MessageFormatter.FormatError("This command must be used in reply to a message!")); } } } diff --git a/ExampleBots/MediaModeratorPoC/Bot/MediaModBot.cs b/ExampleBots/MediaModeratorPoC/Bot/MediaModBot.cs index 7104114..e6ba269 100644 --- a/ExampleBots/MediaModeratorPoC/Bot/MediaModBot.cs +++ b/ExampleBots/MediaModeratorPoC/Bot/MediaModBot.cs @@ -6,14 +6,15 @@ using System.Text.Encodings.Web; using System.Text.RegularExpressions; using ArcaneLibs.Extensions; using LibMatrix; +using LibMatrix.EventTypes.Spec; +using LibMatrix.EventTypes.Spec.State; using LibMatrix.Helpers; using LibMatrix.Homeservers; using LibMatrix.Responses; using LibMatrix.RoomTypes; using LibMatrix.Services; -using LibMatrix.StateEventTypes.Spec; +using LibMatrix.Utilities.Bot.Interfaces; using MediaModeratorPoC.Bot.AccountData; -using MediaModeratorPoC.Bot.Interfaces; using MediaModeratorPoC.Bot.StateEventTypes; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -86,9 +87,9 @@ public class MediaModBot(AuthenticatedHomeserverGeneric hs, ILogger<MediaModBot> await hs.SetAccountData("gay.rory.media_moderator_poc_data", botData); } - _policyRoom = await hs.GetRoom(botData.PolicyRoom ?? botData.ControlRoom); - _logRoom = await hs.GetRoom(botData.LogRoom ?? botData.ControlRoom); - _controlRoom = await hs.GetRoom(botData.ControlRoom); + _policyRoom = hs.GetRoom(botData.PolicyRoom ?? botData.ControlRoom); + _logRoom = hs.GetRoom(botData.LogRoom ?? botData.ControlRoom); + _controlRoom = hs.GetRoom(botData.ControlRoom); List<string> admins = new(); @@ -113,18 +114,18 @@ public class MediaModBot(AuthenticatedHomeserverGeneric hs, ILogger<MediaModBot> $"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.GetProfile(inviteEvent.Sender); - await (await hs.GetRoom(args.Key)).JoinAsync(reason: $"I was invited by {senderProfile.DisplayName ?? inviteEvent.Sender}!"); + var senderProfile = await hs.GetProfileAsync(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 (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); } } }); hs.SyncHelper.TimelineEventHandlers.Add(async @event => { - var room = await hs.GetRoom(@event.RoomId); + var room = hs.GetRoom(@event.RoomId); try { logger.LogInformation( "Got timeline event in {}: {}", @event.RoomId, @event.ToJson(indent: true, ignoreNull: true)); @@ -136,7 +137,7 @@ public class MediaModBot(AuthenticatedHomeserverGeneric hs, ILogger<MediaModBot> if (matchedPolicy is null) return; var matchedpolicyData = matchedPolicy.TypedContent as MediaPolicyEventContent; var recommendation = matchedpolicyData.Recommendation; - await _logRoom.SendMessageEventAsync("m.room.message", + await _logRoom.SendMessageEventAsync( new RoomMessageEventContent( body: $"User {MessageFormatter.HtmlFormatMention(@event.Sender)} posted an image in {MessageFormatter.HtmlFormatMention(room.RoomId)} that matched rule {matchedPolicy.StateKey}, applying action {matchedpolicyData.Recommendation}, as described in rule: {matchedPolicy.RawContent!.ToJson(ignoreNull: true)}", @@ -147,7 +148,7 @@ public class MediaModBot(AuthenticatedHomeserverGeneric hs, ILogger<MediaModBot> }); switch (recommendation) { case "warn_admins": { - await _controlRoom.SendMessageEventAsync("m.room.message", + await _controlRoom.SendMessageEventAsync( new RoomMessageEventContent( body: $"{string.Join(' ', admins)}\nUser {MessageFormatter.HtmlFormatMention(@event.Sender)} posted a banned image {message.Url}", messageType: "m.text") { @@ -158,7 +159,7 @@ public class MediaModBot(AuthenticatedHomeserverGeneric hs, ILogger<MediaModBot> break; } case "warn": { - await room.SendMessageEventAsync("m.room.message", + await room.SendMessageEventAsync( new RoomMessageEventContent( body: $"Please be careful when posting this image: {matchedpolicyData.Reason}", messageType: "m.text") { @@ -184,7 +185,7 @@ public class MediaModBot(AuthenticatedHomeserverGeneric hs, ILogger<MediaModBot> // <img src=\"mxc://rory.gay/sLkdxUhipiQaFwRkXcPSRwdg\" height=\"69\"></a> // </span> // </blockquote> - await room.SendMessageEventAsync("m.room.message", + await room.SendMessageEventAsync( new RoomMessageEventContent( body: $"Please be careful when posting this image: {matchedpolicyData.Reason}, I have spoilered it for you:", @@ -194,7 +195,7 @@ public class MediaModBot(AuthenticatedHomeserverGeneric hs, ILogger<MediaModBot> $"<font color=\"#FFFF00\">Please be careful when posting this image: {matchedpolicyData.Reason}, I have spoilered it for you:</a></font>" }); var imageUrl = message.Url; - await room.SendMessageEventAsync("m.room.message", + await room.SendMessageEventAsync( new RoomMessageEventContent(body: $"CN: {imageUrl}", messageType: "m.text") { Format = "org.matrix.custom.html", @@ -244,9 +245,9 @@ public class MediaModBot(AuthenticatedHomeserverGeneric hs, ILogger<MediaModBot> } catch (Exception e) { logger.LogError("{}", e.ToString()); - await _controlRoom.SendMessageEventAsync("m.room.message", + await _controlRoom.SendMessageEventAsync( MessageFormatter.FormatException($"Unable to ban user in {MessageFormatter.HtmlFormatMention(room.RoomId)}", e)); - await _logRoom.SendMessageEventAsync("m.room.message", + await _logRoom.SendMessageEventAsync( MessageFormatter.FormatException($"Unable to ban user in {MessageFormatter.HtmlFormatMention(room.RoomId)}", e)); await using var stream = new MemoryStream(e.ToString().AsBytes().ToArray()); await _controlRoom.SendFileAsync("m.file", "error.log.cs", stream); @@ -274,7 +275,7 @@ public class MediaModBot(AuthenticatedHomeserverGeneric hs, ILogger<MediaModBot> fileHash = await hashAlgo.ComputeHashAsync(await hs._httpClient.GetStreamAsync(resolvedUri)); } catch (Exception ex) { - await _logRoom.SendMessageEventAsync("m.room.message", + await _logRoom.SendMessageEventAsync( MessageFormatter.FormatException($"Error calculating file hash for {mxcUri} via {mxcUri.Split('/')[2]} ({resolvedUri}), retrying via {hs.HomeServerDomain}...", ex)); try { @@ -282,7 +283,7 @@ public class MediaModBot(AuthenticatedHomeserverGeneric hs, ILogger<MediaModBot> fileHash = await hashAlgo.ComputeHashAsync(await hs._httpClient.GetStreamAsync(resolvedUri)); } catch (Exception ex2) { - await _logRoom.SendMessageEventAsync("m.room.message", + await _logRoom.SendMessageEventAsync( MessageFormatter.FormatException($"Error calculating file hash via {hs.HomeServerDomain} ({resolvedUri})!", ex2)); } } diff --git a/ExampleBots/PluralContactBotPoC/Bot/Commands/CreateSystemCommand.cs b/ExampleBots/PluralContactBotPoC/Bot/Commands/CreateSystemCommand.cs index 5da4f5e..55624a8 100644 --- a/ExampleBots/PluralContactBotPoC/Bot/Commands/CreateSystemCommand.cs +++ b/ExampleBots/PluralContactBotPoC/Bot/Commands/CreateSystemCommand.cs @@ -1,8 +1,8 @@ using LibMatrix; +using LibMatrix.EventTypes.Spec.State; using LibMatrix.Helpers; using LibMatrix.Services; -using LibMatrix.StateEventTypes.Spec; -using MediaModeratorPoC.Bot.Interfaces; +using LibMatrix.Utilities.Bot.Interfaces; using PluralContactBotPoC.Bot.AccountData; using PluralContactBotPoC.Bot.StateEventTypes; @@ -18,7 +18,7 @@ public class CreateSystemCommand(IServiceProvider services, HomeserverProviderSe public async Task Invoke(CommandContext ctx) { if (ctx.Args.Length != 1) { - await ctx.Reply("m.notice", MessageFormatter.FormatError("Only one argument is allowed: system name!")); + await ctx.Reply(MessageFormatter.FormatError("Only one argument is allowed: system name!")); return; } @@ -26,7 +26,7 @@ public class CreateSystemCommand(IServiceProvider services, HomeserverProviderSe try { try { await ctx.Homeserver.GetAccountData<BotData>("gay.rory.plural_contact_bot.system_data"); - await ctx.Reply("m.notice", MessageFormatter.FormatError($"System {sysName} already exists!")); + await ctx.Reply(MessageFormatter.FormatError($"System {sysName} already exists!")); } catch (MatrixException e) { if (e is { ErrorCode: "M_NOT_FOUND" }) { @@ -51,7 +51,7 @@ public class CreateSystemCommand(IServiceProvider services, HomeserverProviderSe } } catch (Exception e) { - await ctx.Reply("m.notice", MessageFormatter.FormatException("Something went wrong!", e)); + await ctx.Reply(MessageFormatter.FormatException("Something went wrong!", e)); } } } diff --git a/ExampleBots/PluralContactBotPoC/Bot/PluralContactBot.cs b/ExampleBots/PluralContactBotPoC/Bot/PluralContactBot.cs index 2136b42..c3cebe2 100644 --- a/ExampleBots/PluralContactBotPoC/Bot/PluralContactBot.cs +++ b/ExampleBots/PluralContactBotPoC/Bot/PluralContactBot.cs @@ -1,12 +1,13 @@ using System.Text; using ArcaneLibs.Extensions; +using LibMatrix.EventTypes.Spec; +using LibMatrix.EventTypes.Spec.State; using LibMatrix.Helpers; using LibMatrix.Homeservers; using LibMatrix.RoomTypes; using LibMatrix.Services; -using LibMatrix.StateEventTypes.Spec; using LibMatrix.Utilities.Bot; -using MediaModeratorPoC.Bot.Interfaces; +using LibMatrix.Utilities.Bot.Interfaces; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using PluralContactBotPoC.Bot.AccountData; @@ -35,7 +36,7 @@ public class PluralContactBot(AuthenticatedHomeserverGeneric hs, ILogger<PluralC BotData botData; - _logRoom = await hs.GetRoom(botConfiguration.LogRoom); + _logRoom = hs.GetRoom(botConfiguration.LogRoom); hs.SyncHelper.InviteReceivedHandlers.Add(async Task (args) => { var inviteEvent = @@ -46,9 +47,9 @@ public class PluralContactBot(AuthenticatedHomeserverGeneric hs, ILogger<PluralC try { var accountData = await hs.GetAccountData<SystemData>($"gay.rory.plural_contact_bot.system_data#{inviteEvent.StateKey}"); if (accountData.Members.Contains(inviteEvent.Sender)) { - await (await hs.GetRoom(args.Key)).JoinAsync(reason: "I was invited by a system member!"); + await (hs.GetRoom(args.Key)).JoinAsync(reason: "I was invited by a system member!"); - await _logRoom.SendMessageEventAsync("m.room.message", + await _logRoom.SendMessageEventAsync( MessageFormatter.FormatSuccess( $"I was invited by a system member ({MessageFormatter.HtmlFormatMention(inviteEvent.Sender)}) to {MessageFormatter.HtmlFormatMention(args.Key)}")); @@ -56,25 +57,25 @@ public class PluralContactBot(AuthenticatedHomeserverGeneric hs, ILogger<PluralC } } catch (Exception e) { - await _logRoom.SendMessageEventAsync("m.room.message", + await _logRoom.SendMessageEventAsync( MessageFormatter.FormatException( $"Exception handling event {inviteEvent.EventId} by {inviteEvent.Sender} in {MessageFormatter.HtmlFormatMention(inviteEvent.RoomId)}", e)); } if (inviteEvent.Sender.EndsWith(":rory.gay") || inviteEvent.Sender.EndsWith(":conduit.rory.gay")) { try { - var senderProfile = await hs.GetProfile(inviteEvent.Sender); - await (await hs.GetRoom(args.Key)).JoinAsync(reason: $"I was invited by {senderProfile.DisplayName ?? inviteEvent.Sender}!"); + var senderProfile = await hs.GetProfileAsync(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 (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); } } }); hs.SyncHelper.TimelineEventHandlers.Add(async @event => { - var room = await hs.GetRoom(@event.RoomId); + var room = hs.GetRoom(@event.RoomId); try { logger.LogInformation( "Got timeline event in {}: {}", @event.RoomId, @event.ToJson(indent: true, ignoreNull: true)); @@ -83,7 +84,7 @@ public class PluralContactBot(AuthenticatedHomeserverGeneric hs, ILogger<PluralC } catch (Exception e) { logger.LogError("{}", e.ToString()); - await _logRoom.SendMessageEventAsync("m.room.message", + await _logRoom.SendMessageEventAsync( MessageFormatter.FormatException($"Exception handling event {@event.EventId} by {@event.Sender} in {MessageFormatter.HtmlFormatMention(room.RoomId)}", e)); await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(e.ToString())); await _logRoom.SendFileAsync("m.file", "error.log.cs", stream); diff --git a/ExampleBots/PluralContactBotPoC/Program.cs b/ExampleBots/PluralContactBotPoC/Program.cs index b2e041e..49c6c68 100644 --- a/ExampleBots/PluralContactBotPoC/Program.cs +++ b/ExampleBots/PluralContactBotPoC/Program.cs @@ -5,7 +5,6 @@ using System.Text.Json.Serialization; using ArcaneLibs.Extensions; using LibMatrix.Services; using LibMatrix.Utilities.Bot; -using MediaModeratorPoC.Bot; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using PluralContactBotPoC; diff --git a/LibMatrix/StateEventTypes/Common/MjolnirShortcodeEventData.cs b/LibMatrix/EventTypes/Common/MjolnirShortcodeEventData.cs index ff11be7..9067351 100644 --- a/LibMatrix/StateEventTypes/Common/MjolnirShortcodeEventData.cs +++ b/LibMatrix/EventTypes/Common/MjolnirShortcodeEventData.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; using LibMatrix.Helpers; using LibMatrix.Interfaces; -namespace LibMatrix.StateEventTypes.Common; +namespace LibMatrix.EventTypes.Common; [MatrixEvent(EventName = "org.matrix.mjolnir.shortcode")] public class MjolnirShortcodeEventContent : EventContent { diff --git a/LibMatrix/StateEventTypes/Common/RoomEmotesEventData.cs b/LibMatrix/EventTypes/Common/RoomEmotesEventData.cs index a056eda..abf936c 100644 --- a/LibMatrix/StateEventTypes/Common/RoomEmotesEventData.cs +++ b/LibMatrix/EventTypes/Common/RoomEmotesEventData.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; using LibMatrix.Helpers; using LibMatrix.Interfaces; -namespace LibMatrix.StateEventTypes.Common; +namespace LibMatrix.EventTypes.Common; [MatrixEvent(EventName = "im.ponies.room_emotes")] public class RoomEmotesEventContent : EventContent { diff --git a/LibMatrix/StateEventTypes/Spec/RoomMessageEventData.cs b/LibMatrix/EventTypes/Spec/RoomMessageEventData.cs index a15efe8..b76b176 100644 --- a/LibMatrix/StateEventTypes/Spec/RoomMessageEventData.cs +++ b/LibMatrix/EventTypes/Spec/RoomMessageEventData.cs @@ -1,9 +1,8 @@ using System.Text.Json.Serialization; -using ArcaneLibs.Extensions; using LibMatrix.Helpers; using LibMatrix.Interfaces; -namespace LibMatrix.StateEventTypes.Spec; +namespace LibMatrix.EventTypes.Spec; [MatrixEvent(EventName = "m.room.message")] public class RoomMessageEventContent : EventContent { diff --git a/LibMatrix/StateEventTypes/Spec/CanonicalAliasEventContent.cs b/LibMatrix/EventTypes/Spec/State/CanonicalAliasEventContent.cs index 7a0e84c..71f3d0d 100644 --- a/LibMatrix/StateEventTypes/Spec/CanonicalAliasEventContent.cs +++ b/LibMatrix/EventTypes/Spec/State/CanonicalAliasEventContent.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; using LibMatrix.Helpers; using LibMatrix.Interfaces; -namespace LibMatrix.StateEventTypes.Spec; +namespace LibMatrix.EventTypes.Spec.State; [MatrixEvent(EventName = "m.room.canonical_alias")] public class CanonicalAliasEventContent : EventContent { diff --git a/LibMatrix/StateEventTypes/Spec/GuestAccessEventData.cs b/LibMatrix/EventTypes/Spec/State/GuestAccessEventData.cs index 0709b86..af1b2ce 100644 --- a/LibMatrix/StateEventTypes/Spec/GuestAccessEventData.cs +++ b/LibMatrix/EventTypes/Spec/State/GuestAccessEventData.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; using LibMatrix.Helpers; using LibMatrix.Interfaces; -namespace LibMatrix.StateEventTypes.Spec; +namespace LibMatrix.EventTypes.Spec.State; [MatrixEvent(EventName = "m.room.guest_access")] public class GuestAccessEventContent : EventContent { diff --git a/LibMatrix/StateEventTypes/Spec/HistoryVisibilityEventData.cs b/LibMatrix/EventTypes/Spec/State/HistoryVisibilityEventData.cs index b19dd32..b57ade5 100644 --- a/LibMatrix/StateEventTypes/Spec/HistoryVisibilityEventData.cs +++ b/LibMatrix/EventTypes/Spec/State/HistoryVisibilityEventData.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; using LibMatrix.Helpers; using LibMatrix.Interfaces; -namespace LibMatrix.StateEventTypes.Spec; +namespace LibMatrix.EventTypes.Spec.State; [MatrixEvent(EventName = "m.room.history_visibility")] public class HistoryVisibilityEventContent : EventContent { diff --git a/LibMatrix/StateEventTypes/Spec/JoinRulesEventData.cs b/LibMatrix/EventTypes/Spec/State/JoinRulesEventData.cs index 8c0772f..0098bef 100644 --- a/LibMatrix/StateEventTypes/Spec/JoinRulesEventData.cs +++ b/LibMatrix/EventTypes/Spec/State/JoinRulesEventData.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; using LibMatrix.Helpers; using LibMatrix.Interfaces; -namespace LibMatrix.StateEventTypes.Spec; +namespace LibMatrix.EventTypes.Spec.State; [MatrixEvent(EventName = "m.room.join_rules")] public class JoinRulesEventContent : EventContent { diff --git a/LibMatrix/StateEventTypes/Spec/PolicyRuleStateEventData.cs b/LibMatrix/EventTypes/Spec/State/PolicyRuleStateEventData.cs index 539e371..fde02c1 100644 --- a/LibMatrix/StateEventTypes/Spec/PolicyRuleStateEventData.cs +++ b/LibMatrix/EventTypes/Spec/State/PolicyRuleStateEventData.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; using LibMatrix.Helpers; using LibMatrix.Interfaces; -namespace LibMatrix.StateEventTypes.Spec; +namespace LibMatrix.EventTypes.Spec.State; [MatrixEvent(EventName = "m.policy.rule.user")] [MatrixEvent(EventName = "m.policy.rule.server")] diff --git a/LibMatrix/StateEventTypes/Spec/PresenceStateEventData.cs b/LibMatrix/EventTypes/Spec/State/PresenceStateEventData.cs index b897ff0..b12da5b 100644 --- a/LibMatrix/StateEventTypes/Spec/PresenceStateEventData.cs +++ b/LibMatrix/EventTypes/Spec/State/PresenceStateEventData.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; using LibMatrix.Helpers; using LibMatrix.Interfaces; -namespace LibMatrix.StateEventTypes.Spec; +namespace LibMatrix.EventTypes.Spec.State; [MatrixEvent(EventName = "m.presence")] public class PresenceEventContent : EventContent { diff --git a/LibMatrix/StateEventTypes/Spec/ProfileResponseEventData.cs b/LibMatrix/EventTypes/Spec/State/ProfileResponseEventData.cs index 9b4f1d0..893fce1 100644 --- a/LibMatrix/StateEventTypes/Spec/ProfileResponseEventData.cs +++ b/LibMatrix/EventTypes/Spec/State/ProfileResponseEventData.cs @@ -1,7 +1,7 @@ using System.Text.Json.Serialization; using LibMatrix.Interfaces; -namespace LibMatrix.StateEventTypes.Spec; +namespace LibMatrix.EventTypes.Spec.State; public class ProfileResponseEventContent : EventContent { [JsonPropertyName("avatar_url")] diff --git a/LibMatrix/StateEventTypes/Spec/RoomAliasEventData.cs b/LibMatrix/EventTypes/Spec/State/RoomAliasEventData.cs index d3960a1..5b0e914 100644 --- a/LibMatrix/StateEventTypes/Spec/RoomAliasEventData.cs +++ b/LibMatrix/EventTypes/Spec/State/RoomAliasEventData.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; using LibMatrix.Helpers; using LibMatrix.Interfaces; -namespace LibMatrix.StateEventTypes.Spec; +namespace LibMatrix.EventTypes.Spec.State; [MatrixEvent(EventName = "m.room.alias")] public class RoomAliasEventContent : EventContent { diff --git a/LibMatrix/StateEventTypes/Spec/RoomAvatarEventData.cs b/LibMatrix/EventTypes/Spec/State/RoomAvatarEventData.cs index e2263c8..601d014 100644 --- a/LibMatrix/StateEventTypes/Spec/RoomAvatarEventData.cs +++ b/LibMatrix/EventTypes/Spec/State/RoomAvatarEventData.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; using LibMatrix.Helpers; using LibMatrix.Interfaces; -namespace LibMatrix.StateEventTypes.Spec; +namespace LibMatrix.EventTypes.Spec.State; [MatrixEvent(EventName = "m.room.avatar")] public class RoomAvatarEventContent : EventContent { diff --git a/LibMatrix/StateEventTypes/Spec/RoomCreateEventData.cs b/LibMatrix/EventTypes/Spec/State/RoomCreateEventData.cs index 22df784..e409f3a 100644 --- a/LibMatrix/StateEventTypes/Spec/RoomCreateEventData.cs +++ b/LibMatrix/EventTypes/Spec/State/RoomCreateEventData.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; using LibMatrix.Helpers; using LibMatrix.Interfaces; -namespace LibMatrix.StateEventTypes.Spec; +namespace LibMatrix.EventTypes.Spec.State; [MatrixEvent(EventName = "m.room.create")] public class RoomCreateEventContent : EventContent { diff --git a/LibMatrix/StateEventTypes/Spec/RoomEncryptionEventData.cs b/LibMatrix/EventTypes/Spec/State/RoomEncryptionEventData.cs index 1d5ec2c..6ffa4c5 100644 --- a/LibMatrix/StateEventTypes/Spec/RoomEncryptionEventData.cs +++ b/LibMatrix/EventTypes/Spec/State/RoomEncryptionEventData.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; using LibMatrix.Helpers; using LibMatrix.Interfaces; -namespace LibMatrix.StateEventTypes.Spec; +namespace LibMatrix.EventTypes.Spec.State; [MatrixEvent(EventName = "m.room.encryption")] public class RoomEncryptionEventContent : EventContent { diff --git a/LibMatrix/StateEventTypes/Spec/RoomMemberEventData.cs b/LibMatrix/EventTypes/Spec/State/RoomMemberEventData.cs index a9d4710..da158f1 100644 --- a/LibMatrix/StateEventTypes/Spec/RoomMemberEventData.cs +++ b/LibMatrix/EventTypes/Spec/State/RoomMemberEventData.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; using LibMatrix.Helpers; using LibMatrix.Interfaces; -namespace LibMatrix.StateEventTypes.Spec; +namespace LibMatrix.EventTypes.Spec.State; [MatrixEvent(EventName = "m.room.member")] public class RoomMemberEventContent : EventContent { diff --git a/LibMatrix/StateEventTypes/Spec/RoomNameEventData.cs b/LibMatrix/EventTypes/Spec/State/RoomNameEventData.cs index 3002102..7cb881a 100644 --- a/LibMatrix/StateEventTypes/Spec/RoomNameEventData.cs +++ b/LibMatrix/EventTypes/Spec/State/RoomNameEventData.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; using LibMatrix.Helpers; using LibMatrix.Interfaces; -namespace LibMatrix.StateEventTypes.Spec; +namespace LibMatrix.EventTypes.Spec.State; [MatrixEvent(EventName = "m.room.name")] public class RoomNameEventContent : EventContent { diff --git a/LibMatrix/StateEventTypes/Spec/RoomPinnedEventData.cs b/LibMatrix/EventTypes/Spec/State/RoomPinnedEventData.cs index 16144bc..eb02cc7 100644 --- a/LibMatrix/StateEventTypes/Spec/RoomPinnedEventData.cs +++ b/LibMatrix/EventTypes/Spec/State/RoomPinnedEventData.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; using LibMatrix.Helpers; using LibMatrix.Interfaces; -namespace LibMatrix.StateEventTypes.Spec; +namespace LibMatrix.EventTypes.Spec.State; [MatrixEvent(EventName = "m.room.pinned_events")] public class RoomPinnedEventContent : EventContent { diff --git a/LibMatrix/StateEventTypes/Spec/RoomPowerLevelEventData.cs b/LibMatrix/EventTypes/Spec/State/RoomPowerLevelEventData.cs index 960c198..1a5d5f5 100644 --- a/LibMatrix/StateEventTypes/Spec/RoomPowerLevelEventData.cs +++ b/LibMatrix/EventTypes/Spec/State/RoomPowerLevelEventData.cs @@ -2,39 +2,39 @@ using System.Text.Json.Serialization; using LibMatrix.Helpers; using LibMatrix.Interfaces; -namespace LibMatrix.StateEventTypes.Spec; +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; [JsonPropertyName("events")] - public Dictionary<string, long> Events { get; set; } // = null!; + public Dictionary<string, long>? Events { get; set; } // = null!; [JsonPropertyName("invite")] - public long Invite { get; set; } // = 50; + public long? Invite { get; set; } // = 50; [JsonPropertyName("kick")] - public long Kick { get; set; } // = 50; + public long? Kick { get; set; } // = 50; [JsonPropertyName("notifications")] - public NotificationsPL NotificationsPl { get; set; } // = null!; + 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!; + 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] diff --git a/LibMatrix/StateEventTypes/Spec/RoomTopicEventData.cs b/LibMatrix/EventTypes/Spec/State/RoomTopicEventData.cs index 61d1a01..52c7e42 100644 --- a/LibMatrix/StateEventTypes/Spec/RoomTopicEventData.cs +++ b/LibMatrix/EventTypes/Spec/State/RoomTopicEventData.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; using LibMatrix.Helpers; using LibMatrix.Interfaces; -namespace LibMatrix.StateEventTypes.Spec; +namespace LibMatrix.EventTypes.Spec.State; [MatrixEvent(EventName = "m.room.topic")] [MatrixEvent(EventName = "org.matrix.msc3765.topic", Legacy = true)] diff --git a/LibMatrix/StateEventTypes/Spec/RoomTypingEventData.cs b/LibMatrix/EventTypes/Spec/State/RoomTypingEventData.cs index e935cb2..01cfacf 100644 --- a/LibMatrix/StateEventTypes/Spec/RoomTypingEventData.cs +++ b/LibMatrix/EventTypes/Spec/State/RoomTypingEventData.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; using LibMatrix.Helpers; using LibMatrix.Interfaces; -namespace LibMatrix.StateEventTypes.Spec; +namespace LibMatrix.EventTypes.Spec.State; [MatrixEvent(EventName = "m.typing")] public class RoomTypingEventContent : EventContent { diff --git a/LibMatrix/StateEventTypes/Spec/ServerACLEventData.cs b/LibMatrix/EventTypes/Spec/State/ServerACLEventData.cs index 031d113..f18fe43 100644 --- a/LibMatrix/StateEventTypes/Spec/ServerACLEventData.cs +++ b/LibMatrix/EventTypes/Spec/State/ServerACLEventData.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; using LibMatrix.Helpers; using LibMatrix.Interfaces; -namespace LibMatrix.StateEventTypes.Spec; +namespace LibMatrix.EventTypes.Spec.State; [MatrixEvent(EventName = "m.room.server_acl")] public class ServerACLEventContent : EventContent { diff --git a/LibMatrix/StateEventTypes/Spec/SpaceChildEventData.cs b/LibMatrix/EventTypes/Spec/State/SpaceChildEventData.cs index 80fc771..a13ba2e 100644 --- a/LibMatrix/StateEventTypes/Spec/SpaceChildEventData.cs +++ b/LibMatrix/EventTypes/Spec/State/SpaceChildEventData.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; using LibMatrix.Helpers; using LibMatrix.Interfaces; -namespace LibMatrix.StateEventTypes.Spec; +namespace LibMatrix.EventTypes.Spec.State; [MatrixEvent(EventName = "m.space.child")] public class SpaceChildEventContent : EventContent { diff --git a/LibMatrix/StateEventTypes/Spec/SpaceParentEventData.cs b/LibMatrix/EventTypes/Spec/State/SpaceParentEventData.cs index 1bc89ab..0ffa193 100644 --- a/LibMatrix/StateEventTypes/Spec/SpaceParentEventData.cs +++ b/LibMatrix/EventTypes/Spec/State/SpaceParentEventData.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; using LibMatrix.Helpers; using LibMatrix.Interfaces; -namespace LibMatrix.StateEventTypes.Spec; +namespace LibMatrix.EventTypes.Spec.State; [MatrixEvent(EventName = "m.space.parent")] public class SpaceParentEventContent : EventContent { diff --git a/LibMatrix/StateEventTypes/UnknownStateEventData.cs b/LibMatrix/EventTypes/UnknownStateEventData.cs index 59d8fd4..9a276c8 100644 --- a/LibMatrix/StateEventTypes/UnknownStateEventData.cs +++ b/LibMatrix/EventTypes/UnknownStateEventData.cs @@ -1,6 +1,6 @@ using LibMatrix.Interfaces; -namespace LibMatrix.StateEventTypes; +namespace LibMatrix.EventTypes; public class UnknownEventContent : EventContent { diff --git a/LibMatrix/Extensions/HttpClientExtensions.cs b/LibMatrix/Extensions/HttpClientExtensions.cs index 31ae650..a5eb40f 100644 --- a/LibMatrix/Extensions/HttpClientExtensions.cs +++ b/LibMatrix/Extensions/HttpClientExtensions.cs @@ -1,6 +1,8 @@ using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Net.Http.Headers; using System.Reflection; +using System.Text; using System.Text.Json; using ArcaneLibs.Extensions; @@ -28,7 +30,8 @@ public class MatrixHttpClient : HttpClient { if (request.RequestUri is null) throw new NullReferenceException("RequestUri is null"); if (AssertedUserId is not null) request.RequestUri = request.RequestUri.AddQuery("user_id", AssertedUserId); - Console.WriteLine($"Sending request to {request.RequestUri}"); + // Console.WriteLine($"Sending request to {request.RequestUri}"); + try { var webAssemblyEnableStreamingResponseKey = new HttpRequestOptionsKey<bool>("WebAssemblyEnableStreamingResponse"); @@ -76,4 +79,11 @@ public class MatrixHttpClient : HttpClient { response.EnsureSuccessStatusCode(); return await response.Content.ReadAsStreamAsync(cancellationToken); } + + public new async Task<HttpResponseMessage> PutAsJsonAsync<T>([StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, T value, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default) { + var request = new HttpRequestMessage(HttpMethod.Put, requestUri); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + request.Content = new StringContent(JsonSerializer.Serialize(value, value.GetType()), Encoding.UTF8, "application/json"); + return await SendAsync(request, cancellationToken); + } } diff --git a/LibMatrix/Extensions/StringExtensions.cs b/LibMatrix/Extensions/StringExtensions.cs deleted file mode 100644 index 491fa77..0000000 --- a/LibMatrix/Extensions/StringExtensions.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace LibMatrix.Extensions; - -public static class StringExtensions { - // public static async Task<string> GetMediaUrl(this string MxcUrl) - // { - // //MxcUrl: mxc://rory.gay/ocRVanZoUTCcifcVNwXgbtTg - // //target: https://matrix.rory.gay/_matrix/media/v3/download/rory.gay/ocRVanZoUTCcifcVNwXgbtTg - // - // var server = MxcUrl.Split('/')[2]; - // var mediaId = MxcUrl.Split('/')[3]; - // return $"{(await new RemoteHomeServer(server).Configure()).FullHomeServerDomain}/_matrix/media/v3/download/{server}/{mediaId}"; - // } -} diff --git a/LibMatrix/Helpers/MediaResolver.cs b/LibMatrix/Helpers/MediaResolver.cs deleted file mode 100644 index 5886618..0000000 --- a/LibMatrix/Helpers/MediaResolver.cs +++ /dev/null @@ -1,7 +0,0 @@ -using LibMatrix.Services; - -namespace LibMatrix.Helpers; - -public static class MediaResolver { - public static string ResolveMediaUri(string homeserver, string mxc) => mxc.Replace("mxc://", $"{homeserver}/_matrix/media/v3/download/"); -} diff --git a/LibMatrix/Helpers/MessageFormatter.cs b/LibMatrix/Helpers/MessageFormatter.cs index 37d7004..ae02afc 100644 --- a/LibMatrix/Helpers/MessageFormatter.cs +++ b/LibMatrix/Helpers/MessageFormatter.cs @@ -1,5 +1,5 @@ using ArcaneLibs.Extensions; -using LibMatrix.StateEventTypes.Spec; +using LibMatrix.EventTypes.Spec; namespace LibMatrix.Helpers; diff --git a/LibMatrix/Helpers/SyncHelper.cs b/LibMatrix/Helpers/SyncHelper.cs index d719184..386fd4d 100644 --- a/LibMatrix/Helpers/SyncHelper.cs +++ b/LibMatrix/Helpers/SyncHelper.cs @@ -9,17 +9,17 @@ using LibMatrix.Services; namespace LibMatrix.Helpers; -public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, TieredStorageService storageService) { +public class SyncHelper(AuthenticatedHomeserverGeneric homeserver) { public async Task<SyncResult?> Sync( string? since = null, int? timeout = 30000, string? setPresence = "online", SyncFilter? filter = null, CancellationToken? cancellationToken = null) { - var outFileName = "sync-" + - (await storageService.CacheStorageProvider.GetAllKeysAsync()).Count( - x => x.StartsWith("sync")) + - ".json"; + // 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)}"; @@ -65,10 +65,10 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, TieredStorage SyncFilter? filter = null, CancellationToken? cancellationToken = null ) { - await Task.WhenAll((await storageService.CacheStorageProvider.GetAllKeysAsync()) - .Where(x => x.StartsWith("sync")) - .ToList() - .Select(x => storageService.CacheStorageProvider.DeleteObjectAsync(x))); + // await Task.WhenAll((await storageService.CacheStorageProvider.GetAllKeysAsync()) + // .Where(x => x.StartsWith("sync")) + // .ToList() + // .Select(x => storageService.CacheStorageProvider.DeleteObjectAsync(x))); var nextBatch = since; while (cancellationToken is null || !cancellationToken.Value.IsCancellationRequested) { var sync = await Sync(since: nextBatch, timeout: timeout, setPresence: setPresence, filter: filter, diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs index a280c54..b881e6c 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs @@ -12,25 +12,26 @@ using LibMatrix.Services; namespace LibMatrix.Homeservers; public class AuthenticatedHomeserverGeneric : RemoteHomeServer { - public AuthenticatedHomeserverGeneric(TieredStorageService storage, string canonicalHomeServerDomain, string accessToken) : base(canonicalHomeServerDomain) { - Storage = storage; + public AuthenticatedHomeserverGeneric(string canonicalHomeServerDomain, string accessToken) : base(canonicalHomeServerDomain) { AccessToken = accessToken.Trim(); - SyncHelper = new SyncHelper(this, storage); + SyncHelper = new SyncHelper(this); } - public virtual TieredStorageService Storage { get; set; } public virtual SyncHelper SyncHelper { get; init; } public virtual WhoAmIResponse WhoAmI { get; set; } = null!; public virtual string UserId => WhoAmI.UserId; public virtual string AccessToken { get; set; } - public virtual Task<GenericRoom> GetRoom(string roomId) => Task.FromResult<GenericRoom>(new(this, roomId)); + public virtual GenericRoom GetRoom(string roomId) { + if(roomId is null || !roomId.StartsWith("!")) throw new ArgumentException("Room ID must start with !", nameof(roomId)); + return new GenericRoom(this, roomId); + } public virtual async Task<List<GenericRoom>> GetJoinedRooms() { var roomQuery = await _httpClient.GetAsync("/_matrix/client/v3/joined_rooms"); var roomsJson = await roomQuery.Content.ReadFromJsonAsync<JsonElement>(); - var rooms = roomsJson.GetProperty("joined_rooms").EnumerateArray().Select(room => new GenericRoom(this, room.GetString()!)).ToList(); + var rooms = roomsJson.GetProperty("joined_rooms").EnumerateArray().Select(room => GetRoom(room.GetString()!)).ToList(); Console.WriteLine($"Fetched {rooms.Count} rooms"); @@ -58,7 +59,7 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeServer { throw new InvalidDataException($"Failed to create room: {await res.Content.ReadAsStringAsync()}"); } - var room = await GetRoom((await res.Content.ReadFromJsonAsync<JsonObject>())!["room_id"]!.ToString()); + var room = GetRoom((await res.Content.ReadFromJsonAsync<JsonObject>())!["room_id"]!.ToString()); foreach (var user in creationEvent.Invite) { await room.InviteUser(user); @@ -67,6 +68,14 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeServer { return room; } + public virtual async Task Logout() { + var res = await _httpClient.PostAsync("/_matrix/client/v3/logout", null); + if (!res.IsSuccessStatusCode) { + Console.WriteLine($"Failed to logout: {await res.Content.ReadAsStringAsync()}"); + throw new InvalidDataException($"Failed to logout: {await res.Content.ReadAsStringAsync()}"); + } + } + #region Utility Functions public virtual async IAsyncEnumerable<GenericRoom> GetJoinedRoomsByType(string type) { var rooms = await GetJoinedRooms(); diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverMxApiExtended.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverMxApiExtended.cs index e44d727..5319f46 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverMxApiExtended.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverMxApiExtended.cs @@ -4,5 +4,5 @@ using LibMatrix.Services; namespace LibMatrix.Homeservers; -public class AuthenticatedHomeserverMxApiExtended(TieredStorageService storage, string canonicalHomeServerDomain, string accessToken) : AuthenticatedHomeserverGeneric(storage, canonicalHomeServerDomain, +public class AuthenticatedHomeserverMxApiExtended(string canonicalHomeServerDomain, string accessToken) : AuthenticatedHomeserverGeneric(canonicalHomeServerDomain, accessToken); diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs index 218ded0..ae26f69 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs @@ -102,7 +102,7 @@ public class AuthenticatedHomeserverSynapse : AuthenticatedHomeserverGeneric { } } - public AuthenticatedHomeserverSynapse(TieredStorageService storage, string canonicalHomeServerDomain, string accessToken) : base(storage, canonicalHomeServerDomain, accessToken) { + public AuthenticatedHomeserverSynapse(string canonicalHomeServerDomain, string accessToken) : base(canonicalHomeServerDomain, accessToken) { Admin = new(this); } } diff --git a/LibMatrix/Homeservers/RemoteHomeServer.cs b/LibMatrix/Homeservers/RemoteHomeServer.cs index caed397..ab3ab51 100644 --- a/LibMatrix/Homeservers/RemoteHomeServer.cs +++ b/LibMatrix/Homeservers/RemoteHomeServer.cs @@ -1,7 +1,9 @@ using System.Net.Http.Json; +using System.Text.Json.Serialization; +using ArcaneLibs.Extensions; +using LibMatrix.EventTypes.Spec.State; using LibMatrix.Extensions; using LibMatrix.Responses; -using LibMatrix.StateEventTypes.Spec; namespace LibMatrix.Homeservers; @@ -13,12 +15,13 @@ public class RemoteHomeServer(string canonicalHomeServerDomain) { public string FullHomeServerDomain { get; set; } public MatrixHttpClient _httpClient { get; set; } = new(); - public async Task<ProfileResponseEventContent> GetProfile(string mxid) { - if(mxid is null) throw new ArgumentNullException(nameof(mxid)); + public async Task<ProfileResponseEventContent> GetProfileAsync(string mxid) { + if (mxid is null) throw new ArgumentNullException(nameof(mxid)); if (_profileCache.TryGetValue(mxid, out var value)) { if (value is SemaphoreSlim s) await s.WaitAsync(); if (value is ProfileResponseEventContent p) return p; } + _profileCache[mxid] = new SemaphoreSlim(1); var resp = await _httpClient.GetAsync($"/_matrix/client/v3/profile/{mxid}"); @@ -29,10 +32,26 @@ public class RemoteHomeServer(string canonicalHomeServerDomain) { return data; } - public async Task<ClientVersionsResponse> GetClientVersions() { + public async Task<ClientVersionsResponse> GetClientVersionsAsync() { var resp = await _httpClient.GetAsync($"/_matrix/client/versions"); var data = await resp.Content.ReadFromJsonAsync<ClientVersionsResponse>(); if (!resp.IsSuccessStatusCode) Console.WriteLine("ClientVersions: " + data); return data; } + + public async Task<AliasResult> ResolveRoomAliasAsync(string alias) { + var resp = await _httpClient.GetAsync($"/_matrix/client/v3/directory/room/{alias.Replace("#", "%23")}"); + var data = await resp.Content.ReadFromJsonAsync<AliasResult>(); + var text = await resp.Content.ReadAsStringAsync(); + if (!resp.IsSuccessStatusCode) Console.WriteLine("ResolveAlias: " + data.ToJson()); + return data; + } +} + +public class AliasResult { + [JsonPropertyName("room_id")] + public string RoomId { get; set; } = null!; + + [JsonPropertyName("servers")] + public List<string> Servers { get; set; } = null!; } diff --git a/LibMatrix/Interfaces/IStateEventType.cs b/LibMatrix/Interfaces/IStateEventType.cs index f2e4a3b..b187970 100644 --- a/LibMatrix/Interfaces/IStateEventType.cs +++ b/LibMatrix/Interfaces/IStateEventType.cs @@ -4,10 +4,10 @@ namespace LibMatrix.Interfaces; public abstract class EventContent { [JsonPropertyName("m.relates_to")] - public virtual MessageRelatesTo? RelatesTo { get; set; } + public MessageRelatesTo? RelatesTo { get; set; } [JsonPropertyName("m.new_content")] - public virtual EventContent? NewContent { get; set; } + public EventContent? NewContent { get; set; } public abstract class MessageRelatesTo { [JsonPropertyName("m.in_reply_to")] diff --git a/LibMatrix/Responses/CreateRoomRequest.cs b/LibMatrix/Responses/CreateRoomRequest.cs index 82a4b12..381271b 100644 --- a/LibMatrix/Responses/CreateRoomRequest.cs +++ b/LibMatrix/Responses/CreateRoomRequest.cs @@ -2,10 +2,10 @@ using System.Reflection; using System.Text.Json.Nodes; using System.Text.Json.Serialization; using System.Text.RegularExpressions; +using LibMatrix.EventTypes.Spec.State; using LibMatrix.Helpers; using LibMatrix.Homeservers; using LibMatrix.Interfaces; -using LibMatrix.StateEventTypes.Spec; namespace LibMatrix.Responses; diff --git a/LibMatrix/Responses/StateEventResponse.cs b/LibMatrix/Responses/StateEventResponse.cs index c60d71c..7ca6bab 100644 --- a/LibMatrix/Responses/StateEventResponse.cs +++ b/LibMatrix/Responses/StateEventResponse.cs @@ -45,3 +45,8 @@ public class StateEventResponse : StateEvent { public JsonObject? PrevContent { get; set; } } } + +public class ChunkedStateEventResponse { + [JsonPropertyName("chunk")] + public List<StateEventResponse>? Chunk { get; set; } +} diff --git a/LibMatrix/RoomTypes/GenericRoom.cs b/LibMatrix/RoomTypes/GenericRoom.cs index 146b5dd..ab748fe 100644 --- a/LibMatrix/RoomTypes/GenericRoom.cs +++ b/LibMatrix/RoomTypes/GenericRoom.cs @@ -2,10 +2,12 @@ using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Serialization; using System.Web; +using LibMatrix.EventTypes.Spec; +using LibMatrix.EventTypes.Spec.State; using LibMatrix.Extensions; using LibMatrix.Homeservers; +using LibMatrix.Interfaces; using LibMatrix.Responses; -using LibMatrix.StateEventTypes.Spec; using Microsoft.Extensions.Logging; namespace LibMatrix.RoomTypes; @@ -65,12 +67,12 @@ public class GenericRoom { #endif } catch (MatrixException e) { - if (e is not { ErrorCode: "M_NOT_FOUND" }) { + // if (e is not { ErrorCodode: "M_NOT_FOUND" }) { throw; - } + // } - Console.WriteLine(e); - return default; + // Console.WriteLine(e); + // return default; } } @@ -93,7 +95,7 @@ public class GenericRoom { } } - public async Task JoinAsync(string[]? homeservers = null, string? reason = null) { + public async Task<RoomIdResponse> JoinAsync(string[]? homeservers = null, string? reason = null) { var join_url = $"/_matrix/client/v3/join/{HttpUtility.UrlEncode(RoomId)}"; Console.WriteLine($"Calling {join_url} with {homeservers?.Length ?? 0} via's..."); if (homeservers == null || homeservers.Length == 0) homeservers = new[] { RoomId.Split(':')[1] }; @@ -101,6 +103,7 @@ public class GenericRoom { var res = await _httpClient.PostAsJsonAsync(fullJoinUrl, new { reason }); + return await res.Content.ReadFromJsonAsync<RoomIdResponse>() ?? throw new Exception("Failed to join room?"); } // TODO: rewrite (members endpoint?) @@ -111,10 +114,10 @@ public class GenericRoom { // if (joinedOnly && (member.TypedContent as RoomMemberEventContent)?.Membership is not "join") continue; // yield return member; // } - var res = await _httpClient.GetAsync($"/_matrix/client/v3/rooms/{RoomId}/members"); - var result = - JsonSerializer.DeserializeAsyncEnumerable<StateEventResponse>(await res.Content.ReadAsStreamAsync()); - await foreach (var resp in result) { + var res = await _httpClient.GetAsync($"/_matrix/client/v3/rooms/{RoomId}/members?limit=2"); + var resText = await res.Content.ReadAsStringAsync(); + var result = await JsonSerializer.DeserializeAsync<ChunkedStateEventResponse>(await res.Content.ReadAsStreamAsync()); + foreach (var resp in result.Chunk) { if (resp?.Type != "m.room.member") continue; if (joinedOnly && (resp.TypedContent as RoomMemberEventContent)?.Membership is not "join") continue; yield return resp; @@ -123,6 +126,9 @@ public class GenericRoom { #region Utility shortcuts + public async Task<EventIdResponse> SendMessageEventAsync(RoomMessageEventContent content) => + await SendTimelineEventAsync("m.room.message", content); + public async Task<List<string>> GetAliasesAsync() { var res = await GetStateAsync<RoomAliasEventContent>("m.room.aliases"); return res.Aliases; @@ -187,7 +193,7 @@ public class GenericRoom { await (await _httpClient.PutAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/state/{eventType}/{stateKey}", content)) .Content.ReadFromJsonAsync<EventIdResponse>(); - public async Task<EventIdResponse> SendMessageEventAsync(string eventType, RoomMessageEventContent content) { + public async Task<EventIdResponse> SendTimelineEventAsync(string eventType, EventContent content) { var res = await _httpClient.PutAsJsonAsync( $"/_matrix/client/v3/rooms/{RoomId}/send/{eventType}/" + Guid.NewGuid(), content, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull @@ -244,3 +250,8 @@ public class GenericRoom { await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/invite", new UserIdAndReason(userId, reason)); } } + +public class RoomIdResponse { + [JsonPropertyName("room_id")] + public string RoomId { get; set; } = null!; +} diff --git a/LibMatrix/RoomTypes/SpaceRoom.cs b/LibMatrix/RoomTypes/SpaceRoom.cs index 0a4447a..a43ae82 100644 --- a/LibMatrix/RoomTypes/SpaceRoom.cs +++ b/LibMatrix/RoomTypes/SpaceRoom.cs @@ -13,14 +13,14 @@ public class SpaceRoom : GenericRoom { private static SemaphoreSlim _semaphore = new(1, 1); public async IAsyncEnumerable<GenericRoom> GetChildrenAsync(bool includeRemoved = false) { - await _semaphore.WaitAsync(); + // 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 await _homeserver.GetRoom(stateEvent.StateKey); + yield return _homeserver.GetRoom(stateEvent.StateKey); } - _semaphore.Release(); + // _semaphore.Release(); } } diff --git a/LibMatrix/Services/HomeserverProviderService.cs b/LibMatrix/Services/HomeserverProviderService.cs index 71d9860..49167fa 100644 --- a/LibMatrix/Services/HomeserverProviderService.cs +++ b/LibMatrix/Services/HomeserverProviderService.cs @@ -39,10 +39,10 @@ public class HomeserverProviderService { AuthenticatedHomeserverGeneric hs; if (true) { - hs = new AuthenticatedHomeserverMxApiExtended(_tieredStorageService, homeserver, accessToken); + hs = new AuthenticatedHomeserverMxApiExtended(homeserver, accessToken); } else { - hs = new AuthenticatedHomeserverGeneric(_tieredStorageService, homeserver, accessToken); + hs = new AuthenticatedHomeserverGeneric(homeserver, accessToken); } hs.FullHomeServerDomain = domain; diff --git a/LibMatrix/Services/HomeserverResolverService.cs b/LibMatrix/Services/HomeserverResolverService.cs index f2c0781..685724b 100644 --- a/LibMatrix/Services/HomeserverResolverService.cs +++ b/LibMatrix/Services/HomeserverResolverService.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.Logging; namespace LibMatrix.Services; -public class HomeserverResolverService(ILogger<HomeserverResolverService>? logger) { +public class HomeserverResolverService(ILogger<HomeserverResolverService>? logger = null) { private readonly MatrixHttpClient _httpClient = new(); private static readonly Dictionary<string, string> _wellKnownCache = new(); diff --git a/LibMatrix/Services/TieredStorageService.cs b/LibMatrix/Services/TieredStorageService.cs index 954a2ce..f242785 100644 --- a/LibMatrix/Services/TieredStorageService.cs +++ b/LibMatrix/Services/TieredStorageService.cs @@ -3,10 +3,10 @@ using LibMatrix.Interfaces.Services; namespace LibMatrix.Services; public class TieredStorageService { - public IStorageProvider CacheStorageProvider { get; } - public IStorageProvider DataStorageProvider { get; } + public IStorageProvider? CacheStorageProvider { get; } + public IStorageProvider? DataStorageProvider { get; } - public TieredStorageService(IStorageProvider cacheStorageProvider, IStorageProvider dataStorageProvider) { + public TieredStorageService(IStorageProvider? cacheStorageProvider, IStorageProvider? dataStorageProvider) { CacheStorageProvider = cacheStorageProvider; DataStorageProvider = dataStorageProvider; } diff --git a/LibMatrix/StateEvent.cs b/LibMatrix/StateEvent.cs index 9ca9141..97348a5 100644 --- a/LibMatrix/StateEvent.cs +++ b/LibMatrix/StateEvent.cs @@ -4,15 +4,14 @@ using System.Text.Json.Nodes; using System.Text.Json.Serialization; using ArcaneLibs; using ArcaneLibs.Extensions; +using LibMatrix.EventTypes; using LibMatrix.Helpers; using LibMatrix.Interfaces; -using LibMatrix.StateEventTypes; namespace LibMatrix; public class StateEvent { - public static readonly List<Type> KnownStateEventTypes = - new ClassCollector<EventContent>().ResolveFromAllAccessibleAssemblies(); + public static List<Type> KnownStateEventTypes { get; } = new ClassCollector<EventContent>().ResolveFromAllAccessibleAssemblies(); public static readonly Dictionary<string, Type> KnownStateEventTypesByName = KnownStateEventTypes.Aggregate( new Dictionary<string, Type>(), diff --git a/Tests/LibMatrix.Tests/Config.cs b/Tests/LibMatrix.Tests/Config.cs new file mode 100644 index 0000000..fb4ef02 --- /dev/null +++ b/Tests/LibMatrix.Tests/Config.cs @@ -0,0 +1,17 @@ +namespace LibMatrix.Tests; + +public class Config { + public string? TestHomeserver { get; set; } = Environment.GetEnvironmentVariable("LIBMATRIX_TEST_HOMESERVER") ?? null; + public string? TestUsername { get; set; } = Environment.GetEnvironmentVariable("LIBMATRIX_TEST_USERNAME") ?? null; + public string? TestPassword { get; set; } = Environment.GetEnvironmentVariable("LIBMATRIX_TEST_PASSWORD") ?? null; + public string? TestRoomId { get; set; } = Environment.GetEnvironmentVariable("LIBMATRIX_TEST_ROOM_ID") ?? null; + public string? TestRoomAlias { get; set; } = Environment.GetEnvironmentVariable("LIBMATRIX_TEST_ROOM_ALIAS") ?? null; + + public Dictionary<string, string> ExpectedHomeserverMappings { get; set; } = new() { + {"matrix.org", "https://matrix-client.matrix.org"}, + {"rory.gay", "https://matrix.rory.gay"} + }; + public Dictionary<string, string> ExpectedAliasMappings { get; set; } = new() { + {"#libmatrix:rory.gay", "!tuiLEoMqNOQezxILzt:rory.gay"} + }; +} diff --git a/Tests/LibMatrix.Tests/Fixtures/TestFixture.cs b/Tests/LibMatrix.Tests/Fixtures/TestFixture.cs new file mode 100644 index 0000000..ef49b3e --- /dev/null +++ b/Tests/LibMatrix.Tests/Fixtures/TestFixture.cs @@ -0,0 +1,37 @@ +using ArcaneLibs.Extensions; +using LibMatrix.Services; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Xunit.Microsoft.DependencyInjection; +using Xunit.Microsoft.DependencyInjection.Abstracts; + +namespace LibMatrix.Tests.Fixtures; + +public class TestFixture : TestBedFixture { + protected override void AddServices(IServiceCollection services, IConfiguration? configuration) { + services.AddSingleton<TieredStorageService>(x => + new TieredStorageService( + cacheStorageProvider: null, + dataStorageProvider: null + ) + ); + + services.AddRoryLibMatrixServices(); + + services.AddSingleton<Config>(config => { + var conf = new Config(); + configuration?.GetSection("Configuration").Bind(conf); + + File.WriteAllText("configuration.json", conf.ToJson()); + + return conf; + }); + } + + protected override ValueTask DisposeAsyncCore() + => new(); + + protected override IEnumerable<TestAppSettings> GetTestAppSettings() { + yield return new TestAppSettings { Filename = "appsettings.json", IsOptional = true }; + } +} diff --git a/Tests/LibMatrix.Tests/LibMatrix.Tests.csproj b/Tests/LibMatrix.Tests/LibMatrix.Tests.csproj index ad9b2fa..630273b 100644 --- a/Tests/LibMatrix.Tests/LibMatrix.Tests.csproj +++ b/Tests/LibMatrix.Tests/LibMatrix.Tests.csproj @@ -10,11 +10,11 @@ </PropertyGroup> <ItemGroup> + <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0"/> - <PackageReference Include="xunit" Version="2.4.2"/> + <PackageReference Include="xunit" Version="2.5.0" /> <PackageReference Include="Xunit.Microsoft.DependencyInjection" Version="7.0.6"/> - <PackageReference Include="Xunit.DependencyInjection" Version="8.8.2" /> - <PackageReference Include="Xunit.DependencyInjection.Logging" Version="8.1.0" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> @@ -23,10 +23,17 @@ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference> + <PackageReference Include="Xunit.SkippableFact" Version="1.4.13" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\..\LibMatrix\LibMatrix.csproj" /> </ItemGroup> + <ItemGroup> + <Content Include="appsettings*.json"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </Content> + </ItemGroup> + </Project> diff --git a/Tests/LibMatrix.Tests/ResolverTest.cs b/Tests/LibMatrix.Tests/ResolverTest.cs deleted file mode 100644 index b1191fe..0000000 --- a/Tests/LibMatrix.Tests/ResolverTest.cs +++ /dev/null @@ -1,10 +0,0 @@ -using LibMatrix.Services; - -namespace LibMatrix.Tests; - -public class ResolverTest { - [Fact] - public void ResolveServer() { - - } -} diff --git a/Tests/LibMatrix.Tests/Tests/AuthTests.cs b/Tests/LibMatrix.Tests/Tests/AuthTests.cs new file mode 100644 index 0000000..72a509d --- /dev/null +++ b/Tests/LibMatrix.Tests/Tests/AuthTests.cs @@ -0,0 +1,52 @@ +using LibMatrix.Services; +using LibMatrix.Tests.Fixtures; +using Xunit.Abstractions; +using Xunit.Microsoft.DependencyInjection.Abstracts; + +namespace LibMatrix.Tests.Tests; + +public class AuthTests : TestBed<TestFixture> { + private readonly TestFixture _fixture; + private readonly HomeserverResolverService _resolver; + private readonly Config _config; + private readonly HomeserverProviderService _provider; + + public AuthTests(ITestOutputHelper testOutputHelper, TestFixture fixture) : base(testOutputHelper, fixture) { + _fixture = fixture; + _resolver = _fixture.GetService<HomeserverResolverService>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverResolverService)}"); + _config = _fixture.GetService<Config>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(Config)}"); + _provider = _fixture.GetService<HomeserverProviderService>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverProviderService)}"); + } + + [Fact] + public async Task LoginWithPassword() { + 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); + Assert.NotNull(hs); + await hs.Logout(); + } + + [Fact] + public async Task LoginWithToken() { + 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); + Assert.NotNull(hs); + Assert.NotNull(hs.WhoAmI); + Assert.NotNull(hs.UserId); + Assert.NotNull(hs.AccessToken); + await hs.Logout(); + } +} diff --git a/Tests/LibMatrix.Tests/Tests/ResolverTest.cs b/Tests/LibMatrix.Tests/Tests/ResolverTest.cs new file mode 100644 index 0000000..345508a --- /dev/null +++ b/Tests/LibMatrix.Tests/Tests/ResolverTest.cs @@ -0,0 +1,54 @@ +using LibMatrix.Services; +using LibMatrix.Tests.Fixtures; +using Xunit.Abstractions; +using Xunit.Microsoft.DependencyInjection.Abstracts; + +namespace LibMatrix.Tests.Tests; + +public class ResolverTest : TestBed<TestFixture> { + private readonly TestFixture _fixture; + private readonly HomeserverResolverService _resolver; + private readonly Config _config; + private readonly HomeserverProviderService _provider; + public ResolverTest(ITestOutputHelper testOutputHelper, TestFixture fixture) : base(testOutputHelper, fixture) { + _fixture = fixture; + _resolver = _fixture.GetService<HomeserverResolverService>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverResolverService)}"); + _config = _fixture.GetService<Config>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(Config)}"); + _provider = _fixture.GetService<HomeserverProviderService>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverProviderService)}"); + } + + [Fact] + public async Task ResolveServer() { + foreach (var (domain, expected) in _config.ExpectedHomeserverMappings) { + var server = await _resolver.ResolveHomeserverFromWellKnown(domain); + Assert.Equal(expected, server); + } + } + + [Fact] + public async Task ResolveMedia() { + var media = await _resolver.ResolveMediaUri("matrix.org", "mxc://matrix.org/eqwrRZRoPpNbcMeUwyXAuVRo"); + Assert.Equal("https://matrix-client.matrix.org/_matrix/media/v3/download/matrix.org/eqwrRZRoPpNbcMeUwyXAuVRo", media); + } + + [Fact] + public async Task ResolveRoomAliasAsync() { + var hs = await _provider.GetRemoteHomeserver("matrix.org"); + var alias = await hs.ResolveRoomAliasAsync("#matrix:matrix.org"); + Assert.Equal("!OGEhHVWSdvArJzumhm:matrix.org", alias.RoomId); + } + + [Fact] + public async Task GetClientVersionsAsync() { + var hs = await _provider.GetRemoteHomeserver("matrix.org"); + var versions = await hs.GetClientVersionsAsync(); + Assert.NotNull(versions); + } + + [Fact] + public async Task GetProfileAsync() { + var hs = await _provider.GetRemoteHomeserver("matrix.org"); + var profile = await hs.GetProfileAsync("@alice-is-:matrix.org"); + Assert.NotNull(profile); + } +} diff --git a/Tests/LibMatrix.Tests/Tests/RoomTests.cs b/Tests/LibMatrix.Tests/Tests/RoomTests.cs new file mode 100644 index 0000000..ef63ec9 --- /dev/null +++ b/Tests/LibMatrix.Tests/Tests/RoomTests.cs @@ -0,0 +1,332 @@ +using ArcaneLibs.Extensions; +using LibMatrix.EventTypes.Spec.State; +using LibMatrix.Homeservers; +using LibMatrix.Services; +using LibMatrix.Tests.Fixtures; +using Xunit.Abstractions; +using Xunit.Microsoft.DependencyInjection.Abstracts; + +namespace LibMatrix.Tests.Tests; + +public class RoomTests : TestBed<TestFixture> { + private readonly TestFixture _fixture; + private readonly HomeserverResolverService _resolver; + private readonly Config _config; + private readonly HomeserverProviderService _provider; + + public RoomTests(ITestOutputHelper testOutputHelper, TestFixture fixture) : base(testOutputHelper, fixture) { + _fixture = fixture; + _resolver = _fixture.GetService<HomeserverResolverService>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverResolverService)}"); + _config = _fixture.GetService<Config>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(Config)}"); + _provider = _fixture.GetService<HomeserverProviderService>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverProviderService)}"); + } + + 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; + } + + [Fact] + public async Task GetJoinedRoomsAsync() { + var hs = await GetHomeserver(); + + var rooms = await hs.GetJoinedRooms(); + Assert.NotNull(rooms); + Assert.NotEmpty(rooms); + Assert.All(rooms, Assert.NotNull); + + await hs.Logout(); + } + + [Fact] + public async Task GetNameAsync() { + var hs = await GetHomeserver(); + var room = hs.GetRoom(_config.TestRoomId); + Assert.NotNull(room); + var name = await room.GetNameAsync(); + Assert.NotNull(name); + Assert.NotEmpty(name); + } + + [SkippableFact(typeof(MatrixException))] + public async Task GetTopicAsync() { + var hs = await GetHomeserver(); + var room = hs.GetRoom(_config.TestRoomId); + Assert.NotNull(room); + var topic = await room.GetTopicAsync(); + Assert.NotNull(topic); + Assert.NotNull(topic.Topic); + Assert.NotEmpty(topic.Topic); + } + + [Fact] + public async Task GetMembersAsync() { + 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); + Assert.NotNull(room); + var members = room.GetMembersAsync(); + Assert.NotNull(members); + bool hitMembers = false; + await foreach (var member in members) { + Assert.NotNull(member); + Assert.NotNull(member.StateKey); + Assert.NotEmpty(member.StateKey); + Assert.NotNull(member.Sender); + Assert.NotEmpty(member.Sender); + Assert.NotNull(member.RawContent); + Assert.NotEmpty(member.RawContent); + Assert.NotNull(member.TypedContent); + Assert.IsType<RoomMemberEventContent>(member.TypedContent); + var content = (RoomMemberEventContent)member.TypedContent; + Assert.NotNull(content); + Assert.NotNull(content.Membership); + Assert.NotEmpty(content.Membership); + hitMembers = true; + } + + 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); + Assert.NotNull(room); + var id = await room.JoinAsync(); + Assert.NotNull(id); + Assert.NotNull(id.RoomId); + Assert.NotEmpty(id.RoomId); + } + + [SkippableFact(typeof(MatrixException))] + public async Task GetAliasesAsync() { + var hs = await GetHomeserver(); + var room = hs.GetRoom(_config.TestRoomId); + Assert.NotNull(room); + var aliases = await room.GetAliasesAsync(); + Assert.NotNull(aliases); + Assert.NotEmpty(aliases); + Assert.All(aliases, Assert.NotNull); + } + + [SkippableFact(typeof(MatrixException))] + public async Task GetCanonicalAliasAsync() { + var hs = await GetHomeserver(); + var room = hs.GetRoom(_config.TestRoomId); + Assert.NotNull(room); + var alias = await room.GetCanonicalAliasAsync(); + Assert.NotNull(alias); + Assert.NotNull(alias.Alias); + Assert.NotEmpty(alias.Alias); + } + + [SkippableFact(typeof(MatrixException))] + public async Task GetAvatarUrlAsync() { + var hs = await GetHomeserver(); + var room = hs.GetRoom(_config.TestRoomId); + Assert.NotNull(room); + var url = await room.GetAvatarUrlAsync(); + Assert.NotNull(url); + Assert.NotNull(url.Url); + Assert.NotEmpty(url.Url); + } + + [Fact] + public async Task GetJoinRuleAsync() { + var hs = await GetHomeserver(); + var room = hs.GetRoom(_config.TestRoomId); + Assert.NotNull(room); + var rule = await room.GetJoinRuleAsync(); + Assert.NotNull(rule); + Assert.NotNull(rule.JoinRule); + Assert.NotEmpty(rule.JoinRule); + } + + [Fact] + public async Task GetHistoryVisibilityAsync() { + var hs = await GetHomeserver(); + var room = hs.GetRoom(_config.TestRoomId); + Assert.NotNull(room); + var visibility = await room.GetHistoryVisibilityAsync(); + Assert.NotNull(visibility); + Assert.NotNull(visibility.HistoryVisibility); + Assert.NotEmpty(visibility.HistoryVisibility); + } + + [Fact] + public async Task GetGuestAccessAsync() { + var hs = await GetHomeserver(); + var room = hs.GetRoom(_config.TestRoomId); + Assert.NotNull(room); + try { + var access = await room.GetGuestAccessAsync(); + Assert.NotNull(access); + Assert.NotNull(access.GuestAccess); + Assert.NotEmpty(access.GuestAccess); + } + catch (Exception e) { + 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); + Assert.NotNull(room); + var create = await room.GetCreateEventAsync(); + Assert.NotNull(create); + Assert.NotNull(create.Creator); + Assert.NotEmpty(create.RoomVersion!); + } + + [Fact] + public async Task GetRoomType() { + var hs = await GetHomeserver(); + var room = hs.GetRoom(_config.TestRoomId); + Assert.NotNull(room); + await room.GetRoomType(); + } + + [Fact] + public async Task GetPowerLevelsAsync() { + var hs = await GetHomeserver(); + var room = hs.GetRoom(_config.TestRoomId); + Assert.NotNull(room); + var power = await room.GetPowerLevelsAsync(); + Assert.NotNull(power); + Assert.NotNull(power.Ban); + Assert.NotNull(power.Kick); + Assert.NotNull(power.Invite); + Assert.NotNull(power.Redact); + Assert.NotNull(power.StateDefault); + Assert.NotNull(power.EventsDefault); + Assert.NotNull(power.UsersDefault); + Assert.NotNull(power.Users); + Assert.NotNull(power.Events); + } + + [Fact(Skip = "This test is destructive!")] + public async Task ForgetAsync() { + var hs = await GetHomeserver(); + var room = hs.GetRoom(_config.TestRoomId); + Assert.NotNull(room); + await room.ForgetAsync(); + } + + [Fact(Skip = "This test is destructive!")] + public async Task LeaveAsync() { + var hs = await GetHomeserver(); + var room = hs.GetRoom(_config.TestRoomId); + Assert.NotNull(room); + await room.LeaveAsync(); + } + + [Fact(Skip = "This test is destructive!")] + public async Task KickAsync() { + var hs = await GetHomeserver(); + var room = hs.GetRoom(_config.TestRoomId); + Assert.NotNull(room); + // await room.KickAsync(_config.TestUserId, "test"); + } + + [Fact(Skip = "This test is destructive!")] + public async Task BanAsync() { + var hs = await GetHomeserver(); + var room = hs.GetRoom(_config.TestRoomId); + Assert.NotNull(room); + // await room.BanAsync(_config.TestUserId, "test"); + } + + [Fact(Skip = "This test is destructive!")] + public async Task UnbanAsync() { + var hs = await GetHomeserver(); + var room = hs.GetRoom(_config.TestRoomId); + Assert.NotNull(room); + // await room.UnbanAsync(_config.TestUserId); + } + + [SkippableFact(typeof(MatrixException))] + public async Task SendStateEventAsync() { + var hs = await GetHomeserver(); + var room = hs.GetRoom(_config.TestRoomId); + 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" + }); + } + + [SkippableFact(typeof(MatrixException))] + public async Task GetStateEventAsync() { + await SendStateEventAsync(); + + var hs = await GetHomeserver(); + var room = hs.GetRoom(_config.TestRoomId); + Assert.NotNull(room); + var state1 = await room.GetStateAsync<ProfileResponseEventContent>("gay.rory.libmatrix.unit_tests"); + Assert.NotNull(state1); + Assert.NotNull(state1.DisplayName); + Assert.NotEmpty(state1.DisplayName); + Assert.NotNull(state1.AvatarUrl); + Assert.NotEmpty(state1.AvatarUrl); + Assert.Equal("wee_woo", state1.DisplayName); + Assert.Equal("no", state1.AvatarUrl); + + var state2 = await room.GetStateAsync<ProfileResponseEventContent>("gay.rory.libmatrix.unit_tests", "state_key_maybe"); + Assert.NotNull(state2); + Assert.NotNull(state2.DisplayName); + Assert.NotEmpty(state2.DisplayName); + Assert.NotNull(state2.AvatarUrl); + Assert.NotEmpty(state2.AvatarUrl); + Assert.Equal("wee_woo", state2.DisplayName); + Assert.Equal("yes", state2.AvatarUrl); + } +} diff --git a/Tests/TestDataGenerator/Bot/DataFetcher.cs b/Tests/TestDataGenerator/Bot/DataFetcher.cs new file mode 100644 index 0000000..b1f8402 --- /dev/null +++ b/Tests/TestDataGenerator/Bot/DataFetcher.cs @@ -0,0 +1,69 @@ +using System.Text; +using System.Threading.Channels; +using ArcaneLibs.Extensions; +using LibMatrix.EventTypes.Spec; +using LibMatrix.EventTypes.Spec.State; +using LibMatrix.Helpers; +using LibMatrix.Homeservers; +using LibMatrix.Interfaces; +using LibMatrix.RoomTypes; +using LibMatrix.Services; +using LibMatrix.Tests; +using LibMatrix.Utilities.Bot; +using LibMatrix.Utilities.Bot.Interfaces; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace PluralContactBotPoC.Bot; + +public class DataFetcher(AuthenticatedHomeserverGeneric hs, ILogger<DataFetcher> logger, LibMatrixBotConfiguration botConfiguration, + // DataFetcherConfiguration configuration, + HomeserverResolverService hsResolver) : IHostedService { + private Task _listenerTask; + + private GenericRoom? _logRoom; + + /// <summary>Triggered when the application host is ready to start the service.</summary> + /// <param name="cancellationToken">Indicates that the start process has been aborted.</param> + public async Task StartAsync(CancellationToken cancellationToken) { + _listenerTask = Run(cancellationToken); + logger.LogInformation("Bot started!"); + } + + private async Task Run(CancellationToken cancellationToken) { + Directory.GetFiles("bot_data/cache").ToList().ForEach(File.Delete); + _logRoom = hs.GetRoom(botConfiguration.LogRoom); + + await _logRoom.SendMessageEventAsync(new RoomMessageEventContent(body: "Test data collector started!")); + await _logRoom.SendMessageEventAsync(new RoomMessageEventContent(body: "Fetching rooms...")); + + var rooms = await hs.GetJoinedRooms(); + await _logRoom.SendMessageEventAsync(new RoomMessageEventContent(body: $"Fetched {rooms.Count} rooms!")); + + await _logRoom.SendMessageEventAsync(new RoomMessageEventContent(body: "Fetching room data...")); + + Config cfg = new Config(); + + var roomAliasTasks = rooms.Select(room => room.GetCanonicalAliasAsync()).ToAsyncEnumerable(); + List<Task<(string, string)>> aliasResolutionTasks = new(); + await foreach (var @event in roomAliasTasks) { + if (@event?.Alias != null) { + await _logRoom.SendMessageEventAsync(new RoomMessageEventContent(body: $"Fetched room alias {(@event).Alias}!")); + aliasResolutionTasks.Add(Task<(string, string)>.Run(async () => { + var alias = await hs.ResolveRoomAliasAsync(@event.Alias); + return (@event.Alias, @alias.RoomId); + }, cancellationToken)); + } + } + var aliasResolutionTaskEnumerator = aliasResolutionTasks.ToAsyncEnumerable(); + await foreach (var result in aliasResolutionTaskEnumerator) { + await _logRoom.SendMessageEventAsync(new RoomMessageEventContent(body: $"Resolved room alias {result.Item1} to {result.Item2}!")); + } + } + + /// <summary>Triggered when the application host is performing a graceful shutdown.</summary> + /// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param> + public async Task StopAsync(CancellationToken cancellationToken) { + logger.LogInformation("Shutting down bot!"); + } +} diff --git a/Tests/TestDataGenerator/Bot/DataFetcherConfiguration.cs b/Tests/TestDataGenerator/Bot/DataFetcherConfiguration.cs new file mode 100644 index 0000000..06df0eb --- /dev/null +++ b/Tests/TestDataGenerator/Bot/DataFetcherConfiguration.cs @@ -0,0 +1,9 @@ +using Microsoft.Extensions.Configuration; + +namespace PluralContactBotPoC.Bot; + +public class DataFetcherConfiguration { + public DataFetcherConfiguration(IConfiguration config) => config.GetRequiredSection("DataFetcher").Bind(this); + + // public string +} diff --git a/Tests/TestDataGenerator/Program.cs b/Tests/TestDataGenerator/Program.cs new file mode 100644 index 0000000..9bd091b --- /dev/null +++ b/Tests/TestDataGenerator/Program.cs @@ -0,0 +1,31 @@ +// See https://aka.ms/new-console-template for more information + +using System.Text.Json; +using System.Text.Json.Serialization; +using ArcaneLibs.Extensions; +using LibMatrix.Services; +using LibMatrix.Utilities.Bot; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using PluralContactBotPoC; +using PluralContactBotPoC.Bot; + +Console.WriteLine("Hello, World!"); + +var host = Host.CreateDefaultBuilder(args).ConfigureServices((_, services) => { + services.AddScoped<TieredStorageService>(x => + new TieredStorageService( + cacheStorageProvider: new FileStorageProvider("bot_data/cache/"), + dataStorageProvider: new FileStorageProvider("bot_data/data/") + ) + ); + // services.AddSingleton<DataFetcherConfiguration>(); + services.AddSingleton<AppServiceConfiguration>(); + + services.AddRoryLibMatrixServices(); + services.AddBot(withCommands: false); + + services.AddHostedService<DataFetcher>(); +}).UseConsoleLifetime().Build(); + +await host.RunAsync(); diff --git a/Tests/TestDataGenerator/Properties/launchSettings.json b/Tests/TestDataGenerator/Properties/launchSettings.json new file mode 100644 index 0000000..997e294 --- /dev/null +++ b/Tests/TestDataGenerator/Properties/launchSettings.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "Default": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + + } + }, + "Development": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + }, + "Local config": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Local" + } + } + } +} diff --git a/Tests/TestDataGenerator/TestDataGenerator.csproj b/Tests/TestDataGenerator/TestDataGenerator.csproj new file mode 100644 index 0000000..1b5a4a9 --- /dev/null +++ b/Tests/TestDataGenerator/TestDataGenerator.csproj @@ -0,0 +1,32 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <OutputType>Exe</OutputType> + <TargetFramework>net7.0</TargetFramework> + <LangVersion>preview</LangVersion> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> + <PublishAot>false</PublishAot> + <InvariantGlobalization>true</InvariantGlobalization> + <!-- <PublishTrimmed>true</PublishTrimmed>--> + <!-- <PublishReadyToRun>true</PublishReadyToRun>--> + <!-- <PublishSingleFile>true</PublishSingleFile>--> + <!-- <PublishReadyToRunShowWarnings>true</PublishReadyToRunShowWarnings>--> + <!-- <PublishTrimmedShowLinkerSizeComparison>true</PublishTrimmedShowLinkerSizeComparison>--> + <!-- <PublishTrimmedShowLinkerSizeComparisonWarnings>true</PublishTrimmedShowLinkerSizeComparisonWarnings>--> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" /> + </ItemGroup> + <ItemGroup> + <Content Include="appsettings*.json"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </Content> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\LibMatrix\LibMatrix.csproj" /> + <ProjectReference Include="..\..\Utilities\LibMatrix.Utilities.Bot\LibMatrix.Utilities.Bot.csproj" /> + <ProjectReference Include="..\LibMatrix.Tests\LibMatrix.Tests.csproj" /> + </ItemGroup> +</Project> diff --git a/Tests/TestDataGenerator/appsettings.Development.json b/Tests/TestDataGenerator/appsettings.Development.json new file mode 100644 index 0000000..38c45c4 --- /dev/null +++ b/Tests/TestDataGenerator/appsettings.Development.json @@ -0,0 +1,18 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + }, + "LibMatrixBot": { + // The homeserver to connect to + "Homeserver": "rory.gay", + // The access token to use + "AccessToken": "syt_xxxxxxxxxxxxxxxxx", + // The command prefix + "Prefix": "?", + "LogRoom": "!xxxxxxxxxxxxxxxxxxxxxx:example.com" + } +} diff --git a/Tests/TestDataGenerator/appsettings.json b/Tests/TestDataGenerator/appsettings.json new file mode 100644 index 0000000..6ba02f3 --- /dev/null +++ b/Tests/TestDataGenerator/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/Utilities/LibMatrix.Utilities.Bot/AppServiceConfiguration.cs b/Utilities/LibMatrix.Utilities.Bot/AppServiceConfiguration.cs index 4de139e..99a789a 100644 --- a/Utilities/LibMatrix.Utilities.Bot/AppServiceConfiguration.cs +++ b/Utilities/LibMatrix.Utilities.Bot/AppServiceConfiguration.cs @@ -1,4 +1,4 @@ -namespace PluralContactBotPoC; +namespace LibMatrix.Utilities.Bot; public class AppServiceConfiguration { public string Id { get; set; } = null!; diff --git a/Utilities/LibMatrix.Utilities.Bot/BotCommandInstaller.cs b/Utilities/LibMatrix.Utilities.Bot/BotCommandInstaller.cs index a13f1bd..16e1444 100644 --- a/Utilities/LibMatrix.Utilities.Bot/BotCommandInstaller.cs +++ b/Utilities/LibMatrix.Utilities.Bot/BotCommandInstaller.cs @@ -3,9 +3,8 @@ using ArcaneLibs.Extensions; using LibMatrix.Helpers; using LibMatrix.Homeservers; using LibMatrix.Services; -using LibMatrix.StateEventTypes.Spec; +using LibMatrix.Utilities.Bot.Interfaces; using LibMatrix.Utilities.Bot.Services; -using MediaModeratorPoC.Bot.Interfaces; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; diff --git a/Utilities/LibMatrix.Utilities.Bot/Commands/HelpCommand.cs b/Utilities/LibMatrix.Utilities.Bot/Commands/HelpCommand.cs index de033ef..4fe1038 100644 --- a/Utilities/LibMatrix.Utilities.Bot/Commands/HelpCommand.cs +++ b/Utilities/LibMatrix.Utilities.Bot/Commands/HelpCommand.cs @@ -1,9 +1,9 @@ using System.Text; -using LibMatrix.StateEventTypes.Spec; -using MediaModeratorPoC.Bot.Interfaces; +using LibMatrix.EventTypes.Spec; +using LibMatrix.Utilities.Bot.Interfaces; using Microsoft.Extensions.DependencyInjection; -namespace MediaModeratorPoC.Bot.Commands; +namespace LibMatrix.Utilities.Bot.Commands; public class HelpCommand(IServiceProvider services) : ICommand { public string Name { get; } = "help"; @@ -17,6 +17,6 @@ public class HelpCommand(IServiceProvider services) : ICommand { sb.AppendLine($"- {command.Name}: {command.Description}"); } - await ctx.Room.SendMessageEventAsync("m.room.message", new RoomMessageEventContent(messageType: "m.notice", body: sb.ToString())); + await ctx.Room.SendMessageEventAsync(new RoomMessageEventContent(messageType: "m.notice", body: sb.ToString())); } } diff --git a/Utilities/LibMatrix.Utilities.Bot/Commands/PingCommand.cs b/Utilities/LibMatrix.Utilities.Bot/Commands/PingCommand.cs index b008be9..16712ea 100644 --- a/Utilities/LibMatrix.Utilities.Bot/Commands/PingCommand.cs +++ b/Utilities/LibMatrix.Utilities.Bot/Commands/PingCommand.cs @@ -1,13 +1,13 @@ -using LibMatrix.StateEventTypes.Spec; -using MediaModeratorPoC.Bot.Interfaces; +using LibMatrix.EventTypes.Spec; +using LibMatrix.Utilities.Bot.Interfaces; -namespace MediaModeratorPoC.Bot.Commands; +namespace LibMatrix.Utilities.Bot.Commands; public class PingCommand : ICommand { public string Name { get; } = "ping"; public string Description { get; } = "Pong!"; public async Task Invoke(CommandContext ctx) { - await ctx.Room.SendMessageEventAsync("m.room.message", new RoomMessageEventContent(body: "pong!")); + await ctx.Room.SendMessageEventAsync(new RoomMessageEventContent(body: "pong!")); } } diff --git a/Utilities/LibMatrix.Utilities.Bot/FileStorageProvider.cs b/Utilities/LibMatrix.Utilities.Bot/FileStorageProvider.cs index d5b991a..39b66e3 100644 --- a/Utilities/LibMatrix.Utilities.Bot/FileStorageProvider.cs +++ b/Utilities/LibMatrix.Utilities.Bot/FileStorageProvider.cs @@ -3,7 +3,7 @@ using ArcaneLibs.Extensions; using LibMatrix.Interfaces.Services; using Microsoft.Extensions.Logging; -namespace MediaModeratorPoC.Bot; +namespace LibMatrix.Utilities.Bot; public class FileStorageProvider : IStorageProvider { private readonly ILogger<FileStorageProvider> _logger; diff --git a/Utilities/LibMatrix.Utilities.Bot/Interfaces/CommandContext.cs b/Utilities/LibMatrix.Utilities.Bot/Interfaces/CommandContext.cs index bdb93d5..97984fd 100644 --- a/Utilities/LibMatrix.Utilities.Bot/Interfaces/CommandContext.cs +++ b/Utilities/LibMatrix.Utilities.Bot/Interfaces/CommandContext.cs @@ -1,10 +1,9 @@ -using LibMatrix; +using LibMatrix.EventTypes.Spec; using LibMatrix.Homeservers; using LibMatrix.Responses; using LibMatrix.RoomTypes; -using LibMatrix.StateEventTypes.Spec; -namespace MediaModeratorPoC.Bot.Interfaces; +namespace LibMatrix.Utilities.Bot.Interfaces; public class CommandContext { public GenericRoom Room { get; set; } @@ -20,5 +19,5 @@ public class CommandContext { public string[] Args => MessageContentWithoutReply.Split(' ')[1..]; public AuthenticatedHomeserverGeneric Homeserver { get; set; } - public async Task<EventIdResponse> Reply(string eventType, RoomMessageEventContent content) => await Room.SendMessageEventAsync(eventType, content); + public async Task<EventIdResponse> Reply(RoomMessageEventContent content) => await Room.SendMessageEventAsync(content); } diff --git a/Utilities/LibMatrix.Utilities.Bot/Interfaces/ICommand.cs b/Utilities/LibMatrix.Utilities.Bot/Interfaces/ICommand.cs index a8fce94..7065683 100644 --- a/Utilities/LibMatrix.Utilities.Bot/Interfaces/ICommand.cs +++ b/Utilities/LibMatrix.Utilities.Bot/Interfaces/ICommand.cs @@ -1,4 +1,4 @@ -namespace MediaModeratorPoC.Bot.Interfaces; +namespace LibMatrix.Utilities.Bot.Interfaces; public interface ICommand { public string Name { get; } diff --git a/Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs b/Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs index df702f4..910db0a 100644 --- a/Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs +++ b/Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs @@ -1,7 +1,7 @@ +using LibMatrix.EventTypes.Spec; using LibMatrix.Helpers; using LibMatrix.Homeservers; -using LibMatrix.StateEventTypes.Spec; -using MediaModeratorPoC.Bot.Interfaces; +using LibMatrix.Utilities.Bot.Interfaces; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -39,7 +39,7 @@ public class CommandListenerHostedService : IHostedService { _logger.LogInformation("Starting command listener!"); _hs.SyncHelper.TimelineEventHandlers.Add(async @event => { try { - var room = await _hs.GetRoom(@event.RoomId); + var room = _hs.GetRoom(@event.RoomId); // _logger.LogInformation(eventResponse.ToJson(indent: false)); if (@event is { Type: "m.room.message", TypedContent: RoomMessageEventContent message }) { if (message is { MessageType: "m.text" }) { @@ -48,7 +48,7 @@ public class CommandListenerHostedService : IHostedService { if (messageContentWithoutReply.StartsWith(_config.Prefix)) { var command = _commands.FirstOrDefault(x => x.Name == messageContentWithoutReply.Split(' ')[0][_config.Prefix.Length..]); if (command == null) { - await room.SendMessageEventAsync("m.room.message", + await room.SendMessageEventAsync( new RoomMessageEventContent(messageType: "m.notice", body: "Command not found!")); return; } @@ -64,12 +64,12 @@ public class CommandListenerHostedService : IHostedService { await command.Invoke(ctx); } catch (Exception e) { - await room.SendMessageEventAsync("m.room.message", + await room.SendMessageEventAsync( MessageFormatter.FormatException("An error occurred during the execution of this command", e)); } } else { - await room.SendMessageEventAsync("m.room.message", + await room.SendMessageEventAsync( new RoomMessageEventContent(messageType: "m.notice", body: "You do not have permission to run this command!")); } } |