diff options
-rw-r--r-- | AccountData/BotData.cs | 2 | ||||
-rw-r--r-- | Commands/BanMediaCommand.cs | 4 | ||||
-rw-r--r-- | Commands/DbgAllRoomsArePolicyListsCommand.cs | 8 | ||||
-rw-r--r-- | Commands/DbgDumpActivePoliciesCommand.cs | 4 | ||||
-rw-r--r-- | Commands/DbgDumpAllStateTypesCommand.cs | 6 | ||||
-rw-r--r-- | Commands/JoinRoomCommand.cs | 8 | ||||
-rw-r--r-- | Commands/JoinSpaceMembersCommand.cs | 6 | ||||
-rw-r--r-- | FirstRunTasks.cs | 2 | ||||
-rw-r--r-- | ModerationBot.cs | 257 | ||||
-rw-r--r-- | ModerationBot.csproj | 1 | ||||
-rw-r--r-- | PolicyEngine.cs | 34 | ||||
-rw-r--r-- | Program.cs | 68 | ||||
-rw-r--r-- | StateEventTypes/Policies/BasePolicy.cs | 4 |
13 files changed, 208 insertions, 196 deletions
diff --git a/AccountData/BotData.cs b/AccountData/BotData.cs index df86589..ab680c2 100644 --- a/AccountData/BotData.cs +++ b/AccountData/BotData.cs @@ -11,4 +11,4 @@ public class BotData { [JsonPropertyName("default_policy_room")] public string? DefaultPolicyRoom { get; set; } -} \ No newline at end of file +} diff --git a/Commands/BanMediaCommand.cs b/Commands/BanMediaCommand.cs index 21e0a94..9e49b22 100644 --- a/Commands/BanMediaCommand.cs +++ b/Commands/BanMediaCommand.cs @@ -20,7 +20,7 @@ public class BanMediaCommand(IServiceProvider services, HomeserverProviderServic //check if user is admin in control room var botData = await ctx.Homeserver.GetAccountDataAsync<BotData>("gay.rory.moderation_bot_data"); var controlRoom = ctx.Homeserver.GetRoom(botData.ControlRoom); - var isAdmin = (await controlRoom.GetPowerLevelsAsync())!.UserHasPermission(ctx.MessageEvent.Sender, "m.room.ban"); + var isAdmin = (await controlRoom.GetPowerLevelsAsync())!.UserHasStatePermission(ctx.MessageEvent.Sender, "m.room.ban"); if (!isAdmin) { // await ctx.Reply("You do not have permission to use this command!"); await ctx.Homeserver.GetRoom(botData.LogRoom!).SendMessageEventAsync( @@ -31,7 +31,7 @@ public class BanMediaCommand(IServiceProvider services, HomeserverProviderServic } public async Task Invoke(CommandContext ctx) { - + var botData = await ctx.Homeserver.GetAccountDataAsync<BotData>("gay.rory.moderation_bot_data"); var policyRoom = ctx.Homeserver.GetRoom(botData.DefaultPolicyRoom ?? botData.ControlRoom); var logRoom = ctx.Homeserver.GetRoom(botData.LogRoom ?? botData.ControlRoom); diff --git a/Commands/DbgAllRoomsArePolicyListsCommand.cs b/Commands/DbgAllRoomsArePolicyListsCommand.cs index 09d3caf..327a9a4 100644 --- a/Commands/DbgAllRoomsArePolicyListsCommand.cs +++ b/Commands/DbgAllRoomsArePolicyListsCommand.cs @@ -26,7 +26,7 @@ public class DbgAllRoomsArePolicyListsCommand //check if user is admin in control room var botData = await ctx.Homeserver.GetAccountDataAsync<BotData>("gay.rory.moderation_bot_data"); var controlRoom = ctx.Homeserver.GetRoom(botData.ControlRoom); - var isAdmin = (await controlRoom.GetPowerLevelsAsync())!.UserHasPermission(ctx.MessageEvent.Sender, "m.room.ban"); + var isAdmin = (await controlRoom.GetPowerLevelsAsync())!.UserHasStatePermission(ctx.MessageEvent.Sender, "m.room.ban"); if (!isAdmin) { // await ctx.Reply("You do not have permission to use this command!"); await ctx.Homeserver.GetRoom(botData.LogRoom!).SendMessageEventAsync( @@ -39,9 +39,9 @@ public class DbgAllRoomsArePolicyListsCommand public async Task Invoke(CommandContext ctx) { var botData = await ctx.Homeserver.GetAccountDataAsync<BotData>("gay.rory.moderation_bot_data"); logRoom = ctx.Homeserver.GetRoom(botData.LogRoom ?? botData.ControlRoom); - + var joinedRooms = await ctx.Homeserver.GetJoinedRooms(); - + await ctx.Homeserver.SetAccountDataAsync("gay.rory.moderation_bot.policy_lists", joinedRooms.ToDictionary(x => x.RoomId, x => new PolicyList() { Trusted = true })); @@ -60,4 +60,4 @@ public class DbgAllRoomsArePolicyListsCommand return true; } -} \ No newline at end of file +} diff --git a/Commands/DbgDumpActivePoliciesCommand.cs b/Commands/DbgDumpActivePoliciesCommand.cs index 395c87c..35c95f8 100644 --- a/Commands/DbgDumpActivePoliciesCommand.cs +++ b/Commands/DbgDumpActivePoliciesCommand.cs @@ -26,7 +26,7 @@ public class DbgDumpActivePoliciesCommand //check if user is admin in control room var botData = await ctx.Homeserver.GetAccountDataAsync<BotData>("gay.rory.moderation_bot_data"); var controlRoom = ctx.Homeserver.GetRoom(botData.ControlRoom); - var isAdmin = (await controlRoom.GetPowerLevelsAsync())!.UserHasPermission(ctx.MessageEvent.Sender, "m.room.ban"); + var isAdmin = (await controlRoom.GetPowerLevelsAsync())!.UserHasStatePermission(ctx.MessageEvent.Sender, "m.room.ban"); if (!isAdmin) { // await ctx.Reply("You do not have permission to use this command!"); await ctx.Homeserver.GetRoom(botData.LogRoom!).SendMessageEventAsync( @@ -40,4 +40,4 @@ public class DbgDumpActivePoliciesCommand await ctx.Room.SendFileAsync("all.json", new MemoryStream(engine.ActivePolicies.ToJson().AsBytes().ToArray()), contentType: "application/json"); await ctx.Room.SendFileAsync("by-type.json", new MemoryStream(engine.ActivePoliciesByType.ToJson().AsBytes().ToArray()), contentType: "application/json"); } -} \ No newline at end of file +} diff --git a/Commands/DbgDumpAllStateTypesCommand.cs b/Commands/DbgDumpAllStateTypesCommand.cs index e9a645e..0013065 100644 --- a/Commands/DbgDumpAllStateTypesCommand.cs +++ b/Commands/DbgDumpAllStateTypesCommand.cs @@ -26,7 +26,7 @@ public class DbgDumpAllStateTypesCommand //check if user is admin in control room var botData = await ctx.Homeserver.GetAccountDataAsync<BotData>("gay.rory.moderation_bot_data"); var controlRoom = ctx.Homeserver.GetRoom(botData.ControlRoom); - var isAdmin = (await controlRoom.GetPowerLevelsAsync())!.UserHasPermission(ctx.MessageEvent.Sender, "m.room.ban"); + var isAdmin = (await controlRoom.GetPowerLevelsAsync())!.UserHasStatePermission(ctx.MessageEvent.Sender, "m.room.ban"); if (!isAdmin) { // await ctx.Reply("You do not have permission to use this command!"); await ctx.Homeserver.GetRoom(botData.LogRoom!).SendMessageEventAsync( @@ -58,7 +58,7 @@ public class DbgDumpAllStateTypesCommand return (memberRoom, SummariseStateTypeCounts(states)); } - + private static (string Raw, string Html) SummariseStateTypeCounts(IList<StateEventResponse> states) { string raw = "Count | State type | Mapped type", html = "<table><tr><th>Count</th><th>State type</th><th>Mapped type</th></tr>"; var groupedStates = states.GroupBy(x => x.Type).ToDictionary(x => x.Key, x => x.ToList()).OrderByDescending(x => x.Value.Count); @@ -70,4 +70,4 @@ public class DbgDumpAllStateTypesCommand html += "</table>"; return (raw, html); } -} \ No newline at end of file +} diff --git a/Commands/JoinRoomCommand.cs b/Commands/JoinRoomCommand.cs index 19a2c54..7496a07 100644 --- a/Commands/JoinRoomCommand.cs +++ b/Commands/JoinRoomCommand.cs @@ -19,7 +19,7 @@ public class JoinRoomCommand(IServiceProvider services, HomeserverProviderServic //check if user is admin in control room var botData = await ctx.Homeserver.GetAccountDataAsync<BotData>("gay.rory.moderation_bot_data"); var controlRoom = ctx.Homeserver.GetRoom(botData.ControlRoom); - var isAdmin = (await controlRoom.GetPowerLevelsAsync())!.UserHasPermission(ctx.MessageEvent.Sender, "m.room.ban"); + var isAdmin = (await controlRoom.GetPowerLevelsAsync())!.UserHasStatePermission(ctx.MessageEvent.Sender, "m.room.ban"); if (!isAdmin) { // await ctx.Reply("You do not have permission to use this command!"); await ctx.Homeserver.GetRoom(botData.LogRoom!).SendMessageEventAsync( @@ -30,16 +30,16 @@ public class JoinRoomCommand(IServiceProvider services, HomeserverProviderServic } public async Task Invoke(CommandContext ctx) { - + var botData = await ctx.Homeserver.GetAccountDataAsync<BotData>("gay.rory.moderation_bot_data"); var policyRoom = ctx.Homeserver.GetRoom(botData.DefaultPolicyRoom ?? botData.ControlRoom); var logRoom = ctx.Homeserver.GetRoom(botData.LogRoom ?? botData.ControlRoom); await logRoom.SendMessageEventAsync(MessageFormatter.FormatSuccess($"Joining room {ctx.Args[0]} with reason: {string.Join(' ', ctx.Args[1..])}")); var roomId = ctx.Args[0]; - var servers = new List<string>() {ctx.Homeserver.ServerName}; + var servers = new List<string>() { ctx.Homeserver.ServerName }; if (roomId.StartsWith('[')) { - + } if (roomId.StartsWith('#')) { diff --git a/Commands/JoinSpaceMembersCommand.cs b/Commands/JoinSpaceMembersCommand.cs index c3b7d12..6e64f6f 100644 --- a/Commands/JoinSpaceMembersCommand.cs +++ b/Commands/JoinSpaceMembersCommand.cs @@ -21,7 +21,7 @@ public class JoinSpaceMembersCommand(IServiceProvider services, HomeserverProvid //check if user is admin in control room var botData = await ctx.Homeserver.GetAccountDataAsync<BotData>("gay.rory.moderation_bot_data"); var controlRoom = ctx.Homeserver.GetRoom(botData.ControlRoom); - var isAdmin = (await controlRoom.GetPowerLevelsAsync())!.UserHasPermission(ctx.MessageEvent.Sender, "m.room.ban"); + var isAdmin = (await controlRoom.GetPowerLevelsAsync())!.UserHasStatePermission(ctx.MessageEvent.Sender, "m.room.ban"); if (!isAdmin) { // await ctx.Reply("You do not have permission to use this command!"); await ctx.Homeserver.GetRoom(botData.LogRoom!).SendMessageEventAsync( @@ -37,9 +37,9 @@ public class JoinSpaceMembersCommand(IServiceProvider services, HomeserverProvid await logRoom.SendMessageEventAsync(MessageFormatter.FormatSuccess($"Joining space children of {ctx.Args[0]} with reason: {string.Join(' ', ctx.Args[1..])}")); var roomId = ctx.Args[0]; - var servers = new List<string>() {ctx.Homeserver.ServerName}; + var servers = new List<string>() { ctx.Homeserver.ServerName }; if (roomId.StartsWith('[')) { - + } if (roomId.StartsWith('#')) { diff --git a/FirstRunTasks.cs b/FirstRunTasks.cs index ebbdc81..83356bf 100644 --- a/FirstRunTasks.cs +++ b/FirstRunTasks.cs @@ -81,4 +81,4 @@ public class FirstRunTasks { return botdata; } -} \ No newline at end of file +} diff --git a/ModerationBot.cs b/ModerationBot.cs index 79b05bf..8a48b61 100644 --- a/ModerationBot.cs +++ b/ModerationBot.cs @@ -10,10 +10,10 @@ using LibMatrix.Responses; using LibMatrix.RoomTypes; using LibMatrix.Services; using LibMatrix.Utilities.Bot.Interfaces; -using ModerationBot.AccountData; -using ModerationBot.StateEventTypes; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using ModerationBot.AccountData; +using ModerationBot.StateEventTypes; using ModerationBot.StateEventTypes.Policies; namespace ModerationBot; @@ -74,9 +74,10 @@ public class ModerationBot(AuthenticatedHomeserverGeneric hs, ILogger<Moderation Task.Run(async () => { while (!cancellationToken.IsCancellationRequested) { var controlRoomMembers = _controlRoom.GetMembersAsync(); + var pls = await _controlRoom.GetPowerLevelsAsync(); await foreach (var member in controlRoomMembers) { if ((member.TypedContent as RoomMemberEventContent)? - .Membership == "join") admins.Add(member.StateKey); + .Membership == "join" && pls.UserHasTimelinePermission(member.Sender, RoomMessageEventContent.EventId)) admins.Add(member.StateKey); } await Task.Delay(TimeSpan.FromMinutes(5), cancellationToken); @@ -104,7 +105,7 @@ public class ModerationBot(AuthenticatedHomeserverGeneric hs, ILogger<Moderation } } }); - + syncHelper.TimelineEventHandlers.Add(async @event => { var room = hs.GetRoom(@event.RoomId); try { @@ -116,7 +117,7 @@ public class ModerationBot(AuthenticatedHomeserverGeneric hs, ILogger<Moderation || @event.GetType.IsAssignableTo(typeof(PolicyRuleEventContent)) )) await engine.ReloadActivePolicyListById(@event.RoomId); - + var rules = await engine.GetMatchingPolicies(@event); foreach (var matchedRule in rules) { await _logRoom.SendMessageEventAsync(MessageFormatter.FormatSuccessJson( @@ -125,131 +126,131 @@ public class ModerationBot(AuthenticatedHomeserverGeneric hs, ILogger<Moderation if (configuration.DemoMode) { // foreach (var matchedRule in rules) { - // await room.SendMessageEventAsync(MessageFormatter.FormatSuccessJson( - // $"{MessageFormatter.HtmlFormatMessageLink(eventId: @event.EventId, roomId: room.RoomId, displayName: "Event")} matched {MessageFormatter.HtmlFormatMessageLink(eventId: @matchedRule.EventId, roomId: matchedRule.RoomId, displayName: "rule")}", @matchedRule.RawContent)); + // await room.SendMessageEventAsync(MessageFormatter.FormatSuccessJson( + // $"{MessageFormatter.HtmlFormatMessageLink(eventId: @event.EventId, roomId: room.RoomId, displayName: "Event")} matched {MessageFormatter.HtmlFormatMessageLink(eventId: @matchedRule.EventId, roomId: matchedRule.RoomId, displayName: "rule")}", @matchedRule.RawContent)); // } return; } -// -// if (@event is { Type: "m.room.message", TypedContent: RoomMessageEventContent message }) { -// if (message is { MessageType: "m.image" }) { -// //check media -// // var matchedPolicy = await CheckMedia(@event); -// var matchedPolicy = rules.FirstOrDefault(); -// if (matchedPolicy is null) return; -// var matchedpolicyData = matchedPolicy.TypedContent as MediaPolicyEventContent; -// 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)}", -// messageType: "m.text") { -// Format = "org.matrix.custom.html", -// FormattedBody = -// $"<font color=\"#FFFF00\">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: <pre>{matchedPolicy.RawContent!.ToJson(ignoreNull: true)}</pre></font>" -// }); -// switch (matchedpolicyData.Recommendation) { -// case "warn_admins": { -// await _controlRoom.SendMessageEventAsync( -// new RoomMessageEventContent( -// body: $"{string.Join(' ', admins)}\nUser {MessageFormatter.HtmlFormatMention(@event.Sender)} posted a banned image {message.Url}", -// messageType: "m.text") { -// Format = "org.matrix.custom.html", -// FormattedBody = $"{string.Join(' ', admins.Select(u => MessageFormatter.HtmlFormatMention(u)))}\n" + -// $"<font color=\"#FF0000\">User {MessageFormatter.HtmlFormatMention(@event.Sender)} posted a banned image <a href=\"{message.Url}\">{message.Url}</a></font>" -// }); -// break; -// } -// case "warn": { -// await room.SendMessageEventAsync( -// new RoomMessageEventContent( -// body: $"Please be careful when posting this image: {matchedpolicyData.Reason ?? "No reason specified"}", -// messageType: "m.text") { -// Format = "org.matrix.custom.html", -// FormattedBody = -// $"<font color=\"#FFFF00\">Please be careful when posting this image: {matchedpolicyData.Reason ?? "No reason specified"}</a></font>" -// }); -// break; -// } -// case "redact": { -// await room.RedactEventAsync(@event.EventId, matchedpolicyData.Reason ?? "No reason specified"); -// break; -// } -// case "spoiler": { -// // <blockquote> -// // <a href=\"https://matrix.to/#/@emma:rory.gay\">@emma:rory.gay</a><br> -// // <a href=\"https://codeberg.org/crimsonfork/CN\"></a> -// // <font color=\"#dc143c\" data-mx-color=\"#dc143c\"> -// // <b>CN</b> -// // </font>: -// // <a href=\"https://the-apothecary.club/_matrix/media/v3/download/rory.gay/sLkdxUhipiQaFwRkXcPSRwdg\">test</a><br> -// // <span data-mx-spoiler=\"\"><a href=\"https://the-apothecary.club/_matrix/media/v3/download/rory.gay/sLkdxUhipiQaFwRkXcPSRwdg\"> -// // <img src=\"mxc://rory.gay/sLkdxUhipiQaFwRkXcPSRwdg\" height=\"69\"></a> -// // </span> -// // </blockquote> -// await room.SendMessageEventAsync( -// new RoomMessageEventContent( -// body: -// $"Please be careful when posting this image: {matchedpolicyData.Reason}, I have spoilered it for you:", -// messageType: "m.text") { -// Format = "org.matrix.custom.html", -// FormattedBody = -// $"<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( -// new RoomMessageEventContent(body: $"CN: {imageUrl}", -// messageType: "m.text") { -// Format = "org.matrix.custom.html", -// FormattedBody = $""" -// <blockquote> -// <font color=\"#dc143c\" data-mx-color=\"#dc143c\"> -// <b>CN</b> -// </font>: -// <a href=\"{imageUrl}\">{matchedpolicyData.Reason}</a><br> -// <span data-mx-spoiler=\"\"> -// <a href=\"{imageUrl}\"> -// <img src=\"{imageUrl}\" height=\"69\"> -// </a> -// </span> -// </blockquote> -// """ -// }); -// await room.RedactEventAsync(@event.EventId, "Automatically spoilered: " + matchedpolicyData.Reason); -// break; -// } -// case "mute": { -// await room.RedactEventAsync(@event.EventId, matchedpolicyData.Reason); -// //change powerlevel to -1 -// var currentPls = await room.GetPowerLevelsAsync(); -// if (currentPls is null) { -// logger.LogWarning("Unable to get power levels for {room}", room.RoomId); -// await _logRoom.SendMessageEventAsync( -// MessageFormatter.FormatError($"Unable to get power levels for {MessageFormatter.HtmlFormatMention(room.RoomId)}")); -// return; -// } -// -// currentPls.Users ??= new(); -// currentPls.Users[@event.Sender] = -1; -// await room.SendStateEventAsync("m.room.power_levels", currentPls); -// break; -// } -// case "kick": { -// await room.RedactEventAsync(@event.EventId, matchedpolicyData.Reason); -// await room.KickAsync(@event.Sender, matchedpolicyData.Reason); -// break; -// } -// case "ban": { -// await room.RedactEventAsync(@event.EventId, matchedpolicyData.Reason); -// await room.BanAsync(@event.Sender, matchedpolicyData.Reason); -// break; -// } -// default: { -// throw new ArgumentOutOfRangeException("recommendation", -// $"Unknown response type {matchedpolicyData.Recommendation}!"); -// } -// } -// } -// } + // + // if (@event is { Type: "m.room.message", TypedContent: RoomMessageEventContent message }) { + // if (message is { MessageType: "m.image" }) { + // //check media + // // var matchedPolicy = await CheckMedia(@event); + // var matchedPolicy = rules.FirstOrDefault(); + // if (matchedPolicy is null) return; + // var matchedpolicyData = matchedPolicy.TypedContent as MediaPolicyEventContent; + // 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)}", + // messageType: "m.text") { + // Format = "org.matrix.custom.html", + // FormattedBody = + // $"<font color=\"#FFFF00\">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: <pre>{matchedPolicy.RawContent!.ToJson(ignoreNull: true)}</pre></font>" + // }); + // switch (matchedpolicyData.Recommendation) { + // case "warn_admins": { + // await _controlRoom.SendMessageEventAsync( + // new RoomMessageEventContent( + // body: $"{string.Join(' ', admins)}\nUser {MessageFormatter.HtmlFormatMention(@event.Sender)} posted a banned image {message.Url}", + // messageType: "m.text") { + // Format = "org.matrix.custom.html", + // FormattedBody = $"{string.Join(' ', admins.Select(u => MessageFormatter.HtmlFormatMention(u)))}\n" + + // $"<font color=\"#FF0000\">User {MessageFormatter.HtmlFormatMention(@event.Sender)} posted a banned image <a href=\"{message.Url}\">{message.Url}</a></font>" + // }); + // break; + // } + // case "warn": { + // await room.SendMessageEventAsync( + // new RoomMessageEventContent( + // body: $"Please be careful when posting this image: {matchedpolicyData.Reason ?? "No reason specified"}", + // messageType: "m.text") { + // Format = "org.matrix.custom.html", + // FormattedBody = + // $"<font color=\"#FFFF00\">Please be careful when posting this image: {matchedpolicyData.Reason ?? "No reason specified"}</a></font>" + // }); + // break; + // } + // case "redact": { + // await room.RedactEventAsync(@event.EventId, matchedpolicyData.Reason ?? "No reason specified"); + // break; + // } + // case "spoiler": { + // // <blockquote> + // // <a href=\"https://matrix.to/#/@emma:rory.gay\">@emma:rory.gay</a><br> + // // <a href=\"https://codeberg.org/crimsonfork/CN\"></a> + // // <font color=\"#dc143c\" data-mx-color=\"#dc143c\"> + // // <b>CN</b> + // // </font>: + // // <a href=\"https://the-apothecary.club/_matrix/media/v3/download/rory.gay/sLkdxUhipiQaFwRkXcPSRwdg\">test</a><br> + // // <span data-mx-spoiler=\"\"><a href=\"https://the-apothecary.club/_matrix/media/v3/download/rory.gay/sLkdxUhipiQaFwRkXcPSRwdg\"> + // // <img src=\"mxc://rory.gay/sLkdxUhipiQaFwRkXcPSRwdg\" height=\"69\"></a> + // // </span> + // // </blockquote> + // await room.SendMessageEventAsync( + // new RoomMessageEventContent( + // body: + // $"Please be careful when posting this image: {matchedpolicyData.Reason}, I have spoilered it for you:", + // messageType: "m.text") { + // Format = "org.matrix.custom.html", + // FormattedBody = + // $"<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( + // new RoomMessageEventContent(body: $"CN: {imageUrl}", + // messageType: "m.text") { + // Format = "org.matrix.custom.html", + // FormattedBody = $""" + // <blockquote> + // <font color=\"#dc143c\" data-mx-color=\"#dc143c\"> + // <b>CN</b> + // </font>: + // <a href=\"{imageUrl}\">{matchedpolicyData.Reason}</a><br> + // <span data-mx-spoiler=\"\"> + // <a href=\"{imageUrl}\"> + // <img src=\"{imageUrl}\" height=\"69\"> + // </a> + // </span> + // </blockquote> + // """ + // }); + // await room.RedactEventAsync(@event.EventId, "Automatically spoilered: " + matchedpolicyData.Reason); + // break; + // } + // case "mute": { + // await room.RedactEventAsync(@event.EventId, matchedpolicyData.Reason); + // //change powerlevel to -1 + // var currentPls = await room.GetPowerLevelsAsync(); + // if (currentPls is null) { + // logger.LogWarning("Unable to get power levels for {room}", room.RoomId); + // await _logRoom.SendMessageEventAsync( + // MessageFormatter.FormatError($"Unable to get power levels for {MessageFormatter.HtmlFormatMention(room.RoomId)}")); + // return; + // } + // + // currentPls.Users ??= new(); + // currentPls.Users[@event.Sender] = -1; + // await room.SendStateEventAsync("m.room.power_levels", currentPls); + // break; + // } + // case "kick": { + // await room.RedactEventAsync(@event.EventId, matchedpolicyData.Reason); + // await room.KickAsync(@event.Sender, matchedpolicyData.Reason); + // break; + // } + // case "ban": { + // await room.RedactEventAsync(@event.EventId, matchedpolicyData.Reason); + // await room.BanAsync(@event.Sender, matchedpolicyData.Reason); + // break; + // } + // default: { + // throw new ArgumentOutOfRangeException("recommendation", + // $"Unknown response type {matchedpolicyData.Recommendation}!"); + // } + // } + // } + // } } catch (Exception e) { logger.LogError("{}", e.ToString()); @@ -272,4 +273,4 @@ public class ModerationBot(AuthenticatedHomeserverGeneric hs, ILogger<Moderation logger.LogInformation("Shutting down bot!"); } -} \ No newline at end of file +} diff --git a/ModerationBot.csproj b/ModerationBot.csproj index 5c8f8ff..99eb0b9 100644 --- a/ModerationBot.csproj +++ b/ModerationBot.csproj @@ -17,7 +17,6 @@ </PropertyGroup> <ItemGroup> -<!-- <ProjectReference Include="..\..\..\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj" />--> <ProjectReference Include="..\..\LibMatrix\LibMatrix.csproj" /> <ProjectReference Include="..\..\Utilities\LibMatrix.Utilities.Bot\LibMatrix.Utilities.Bot.csproj" /> </ItemGroup> diff --git a/PolicyEngine.cs b/PolicyEngine.cs index 5311637..8bfa448 100644 --- a/PolicyEngine.cs +++ b/PolicyEngine.cs @@ -11,9 +11,9 @@ using LibMatrix.Homeservers; using LibMatrix.Interfaces; using LibMatrix.RoomTypes; using LibMatrix.Services; +using Microsoft.Extensions.Logging; using ModerationBot.AccountData; using ModerationBot.StateEventTypes; -using Microsoft.Extensions.Logging; using ModerationBot.StateEventTypes.Policies; using ModerationBot.StateEventTypes.Policies.Implementations; @@ -67,7 +67,7 @@ public class PolicyEngine(AuthenticatedHomeserverGeneric hs, ILogger<ModerationB var progressMsgContent = MessageFormatter.FormatSuccess($"{policyLists.Count}/{PolicyListAccountData.Count} policy lists loaded, " + $"{policyLists.Sum(x => x.Policies.Count)} policies total, {sw.Elapsed} elapsed.") .SetReplaceRelation<RoomMessageEventContent>(progressMessage.EventId); - + _logRoom?.SendMessageEventAsync(progressMsgContent); } } @@ -99,8 +99,8 @@ public class PolicyEngine(AuthenticatedHomeserverGeneric hs, ILogger<ModerationB return policyList; } - - + + public async Task ReloadActivePolicyListById(string roomId) { if (!ActivePolicyLists.Any(x => x.Room.RoomId == roomId)) return; await LoadPolicyListAsync(hs.GetRoom(roomId), ActivePolicyLists.Single(x => x.Room.RoomId == roomId)); @@ -140,7 +140,7 @@ public class PolicyEngine(AuthenticatedHomeserverGeneric hs, ILogger<ModerationB public async Task<List<BasePolicy>> GetMatchingPolicies(StateEventResponse @event) { List<BasePolicy> matchingPolicies = new(); if (@event.Sender == @hs.UserId) return matchingPolicies; //ignore self at all costs - + if (ActivePoliciesByType.TryGetValue(nameof(ServerPolicyRuleEventContent), out var serverPolicies)) { var userServer = @event.Sender.Split(':', 2)[1]; matchingPolicies.AddRange(serverPolicies.Where(x => x.Entity == userServer)); @@ -160,19 +160,19 @@ public class PolicyEngine(AuthenticatedHomeserverGeneric hs, ILogger<ModerationB return matchingPolicies; } -#region Policy matching + #region Policy matching private async Task<List<BasePolicy>> CheckMessageContent(StateEventResponse @event) { var matchedRules = new List<BasePolicy>(); var msgContent = @event.TypedContent as RoomMessageEventContent; - + if (ActivePoliciesByType.TryGetValue(nameof(MessagePolicyContainsText), out var messageContainsPolicies)) foreach (var policy in messageContainsPolicies) { - if((@msgContent?.Body?.ToLowerInvariant().Contains(policy.Entity.ToLowerInvariant()) ?? false) || (@msgContent?.FormattedBody?.ToLowerInvariant().Contains(policy.Entity.ToLowerInvariant()) ?? false)) + if ((@msgContent?.Body?.ToLowerInvariant().Contains(policy.Entity.ToLowerInvariant()) ?? false) || (@msgContent?.FormattedBody?.ToLowerInvariant().Contains(policy.Entity.ToLowerInvariant()) ?? false)) matchedRules.Add(policy); } - - + + return matchedRules; } @@ -238,17 +238,17 @@ public class PolicyEngine(AuthenticatedHomeserverGeneric hs, ILogger<ModerationB //check pixels every 10% of the way through the image using ImageSharp // var image = Image.Load(await _hs._httpClient.GetStreamAsync(resolvedUri)); } - else logger.LogInformation("No active media file policies"); + else logger.LogInformation("No active media file policies"); // logger.LogInformation("{url} did not match any rules", @event.RawContent["url"]); return matchedRules; } -#endregion + #endregion -#region Internal code + #region Internal code -#region Summarisation + #region Summarisation private static (string Raw, string Html) SummariseStateTypeCounts(IList<StateEventResponse> states) { string raw = "Count | State type | Mapped type", html = "<table><tr><th>Count</th><th>State type</th><th>Mapped type</th></tr>"; @@ -262,8 +262,8 @@ public class PolicyEngine(AuthenticatedHomeserverGeneric hs, ILogger<ModerationB return (raw, html); } -#endregion + #endregion -#endregion + #endregion -} \ No newline at end of file +} diff --git a/Program.cs b/Program.cs index b41b0be..d125258 100644 --- a/Program.cs +++ b/Program.cs @@ -1,28 +1,40 @@ -// See https://aka.ms/new-console-template for more information - -using LibMatrix.Services; -using LibMatrix.Utilities.Bot; -using ModerationBot; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -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<ModerationBotConfiguration>(); - - services.AddRoryLibMatrixServices(); - services.AddBot(withCommands: true); - - services.AddSingleton<PolicyEngine>(); - - services.AddHostedService<ModerationBot.ModerationBot>(); -}).UseConsoleLifetime().Build(); - -await host.RunAsync(); +// See https://aka.ms/new-console-template for more information + +using LibMatrix.Services; +using LibMatrix.Utilities.Bot; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using ModerationBot; + +Console.WriteLine("Hello, World!"); + +var builder = Host.CreateDefaultBuilder(args); + +builder.ConfigureHostOptions(host => { + host.ServicesStartConcurrently = true; + host.ServicesStopConcurrently = true; + host.ShutdownTimeout = TimeSpan.FromSeconds(5); +}); + +if (Environment.GetEnvironmentVariable("MODERATIONBOT_APPSETTINGS_PATH") is string path) + builder.ConfigureAppConfiguration(x => x.AddJsonFile(path)); + +var host = builder.ConfigureServices((_, services) => { + services.AddScoped<TieredStorageService>(x => + new TieredStorageService( + cacheStorageProvider: new FileStorageProvider("bot_data/cache/"), + dataStorageProvider: new FileStorageProvider("bot_data/data/") + ) + ); + services.AddSingleton<ModerationBotConfiguration>(); + + services.AddRoryLibMatrixServices(); + services.AddBot(withCommands: true); + + services.AddSingleton<PolicyEngine>(); + + services.AddHostedService<ModerationBot.ModerationBot>(); +}).UseConsoleLifetime().Build(); + +await host.RunAsync(); \ No newline at end of file diff --git a/StateEventTypes/Policies/BasePolicy.cs b/StateEventTypes/Policies/BasePolicy.cs index 94b2f63..21b44b2 100644 --- a/StateEventTypes/Policies/BasePolicy.cs +++ b/StateEventTypes/Policies/BasePolicy.cs @@ -41,12 +41,12 @@ public abstract class BasePolicy : EventContent { set => Expiry = value is null ? null : ((DateTimeOffset)value).ToUnixTimeMilliseconds(); } -#region Internal metadata + #region Internal metadata [JsonIgnore] public PolicyList PolicyList { get; set; } public StateEventResponse OriginalEvent { get; set; } -#endregion + #endregion } |