about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2025-03-12 19:52:15 +0100
committerRory& <root@rory.gay>2025-03-12 19:52:15 +0100
commitbdf058ab5c936463a022f62ffbb55bb71c26e856 (patch)
tree69914accad80f10dd8d87e3738e820c58f042537
parentInitial commit (diff)
downloadMatrixContentFilter-master.tar.xz
More work HEAD master
m---------LibMatrix0
-rw-r--r--MatrixContentFilter.sln230
-rw-r--r--MatrixContentFilter/Abstractions/IContentFilter.cs11
-rw-r--r--MatrixContentFilter/Commands/BanCommand.cs97
-rw-r--r--MatrixContentFilter/Commands/CheckHistoryCommand.cs2
-rw-r--r--MatrixContentFilter/Commands/ConfigureCommand.cs1
-rw-r--r--MatrixContentFilter/Commands/DumpEventCommand.cs6
-rw-r--r--MatrixContentFilter/Commands/GetConfigCommand.cs4
-rw-r--r--MatrixContentFilter/Commands/NewRoomCommand.cs8
-rw-r--r--MatrixContentFilter/Commands/RedactCommand.cs15
-rw-r--r--MatrixContentFilter/EventTypes/FilterConfiguration.cs19
-rw-r--r--MatrixContentFilter/Handlers/Filters/Generic/BasicMessageTypeFilter.cs88
-rw-r--r--MatrixContentFilter/Handlers/Filters/Generic/GenericEventTypeFilter.cs86
-rw-r--r--MatrixContentFilter/Handlers/Filters/ImageFilter.cs35
-rw-r--r--MatrixContentFilter/Handlers/Filters/PendingInviteLimiter.cs92
-rw-r--r--MatrixContentFilter/Handlers/Filters/VideoFilter.cs28
-rw-r--r--MatrixContentFilter/Handlers/InviteHandler.cs33
-rw-r--r--MatrixContentFilter/MatrixContentFilter.csproj9
-rw-r--r--MatrixContentFilter/MatrixContentFilterConfiguration.cs17
-rw-r--r--MatrixContentFilter/Program.cs117
-rw-r--r--MatrixContentFilter/Properties/launchSettings.json7
-rw-r--r--MatrixContentFilter/Services/AsyncActionQueues/AbstractionAsyncActionQueue.cs1
-rw-r--r--MatrixContentFilter/Services/AsyncActionQueues/FiFoAsyncActionQueue.cs2
-rw-r--r--MatrixContentFilter/Services/BotModeSanityCheckService.cs14
-rw-r--r--MatrixContentFilter/Services/ConfigurationService.cs9
-rw-r--r--MatrixContentFilter/Services/InfoCacheService.cs13
-rw-r--r--MatrixContentFilter/Services/MatrixContentFilterBot.cs5
-rw-r--r--MatrixContentFilter/Services/MatrixContentFilterMetrics.cs16
-rw-r--r--MatrixContentFilter/appsettings.Development.json18
l---------result1
30 files changed, 741 insertions, 243 deletions
diff --git a/LibMatrix b/LibMatrix
-Subproject 1cbcf84174f8fdbd021f8e16466d2784e8fdf38
+Subproject cacabe2b1a15bb7492e23d477ec653513e84d26
diff --git a/MatrixContentFilter.sln b/MatrixContentFilter.sln

index 0641ec9..f7cd7ba 100644 --- a/MatrixContentFilter.sln +++ b/MatrixContentFilter.sln
@@ -1,53 +1,53 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# +# Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixContentFilter", "MatrixContentFilter\MatrixContentFilter.csproj", "{29F66829-AF62-4CEB-A05A-4D26A418180E}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LibMatrix", "LibMatrix", "{FC63408D-5EBC-44D0-9199-AA6359D9ACD9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.EventTypes", "LibMatrix\LibMatrix.EventTypes\LibMatrix.EventTypes.csproj", "{A8E886A0-D255-484E-9BC7-CD770E83F187}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ArcaneLibs", "ArcaneLibs", "{976B35D6-28DE-4084-84D9-B1FB0583D232}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix", "LibMatrix\LibMatrix\LibMatrix.csproj", "{02936216-5828-4574-B29C-0193AD4137FE}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpamBot", "SpamBot\SpamBot.csproj", "{20EBD04C-A16C-4091-A1A5-D4AFECC4F609}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{35972DDC-EF9B-4002-80B8-92081E746B2D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs", "LibMatrix\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj", "{ABBBED50-427C-4B66-B392-AD0DFDFD4C03}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Tests", "LibMatrix\Tests\LibMatrix.Tests\LibMatrix.Tests.csproj", "{A922F451-798F-47D7-BB28-ECEB6411FD82}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Blazor.Components", "LibMatrix\ArcaneLibs\ArcaneLibs.Blazor.Components\ArcaneLibs.Blazor.Components.csproj", "{BAED1DD7-05B6-49C3-B589-E647FF4E3D87}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{1C1B86CD-115F-4673-84AE-04EECB916BDA}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Legacy", "LibMatrix\ArcaneLibs\ArcaneLibs.Legacy\ArcaneLibs.Legacy.csproj", "{EBEF521F-6535-41ED-9133-359040BE3EB7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.DebugDataValidationApi", "LibMatrix\Utilities\LibMatrix.DebugDataValidationApi\LibMatrix.DebugDataValidationApi.csproj", "{EB49AC9D-44BE-438D-91D0-B5177A6AFD71}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Logging", "LibMatrix\ArcaneLibs\ArcaneLibs.Logging\ArcaneLibs.Logging.csproj", "{DA78C7E6-FFAD-48F8-A006-AEEB9CDDF336}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.DevTestBot", "LibMatrix\Utilities\LibMatrix.DevTestBot\LibMatrix.DevTestBot.csproj", "{17FC4683-6C73-4322-84CD-142D4EB4BB95}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.StringNormalisation", "LibMatrix\ArcaneLibs\ArcaneLibs.StringNormalisation\ArcaneLibs.StringNormalisation.csproj", "{377E9844-FCC2-44A1-954B-35CB210760E2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.E2eeTestKit", "LibMatrix\Utilities\LibMatrix.E2eeTestKit\LibMatrix.E2eeTestKit.csproj", "{0AAB1D05-0779-44B2-A3E8-71F96DC42B71}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Tests", "LibMatrix\ArcaneLibs\ArcaneLibs.Tests\ArcaneLibs.Tests.csproj", "{A45A9C69-36E8-4EA4-B362-AE23A5EB75D1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.HomeserverEmulator", "LibMatrix\Utilities\LibMatrix.HomeserverEmulator\LibMatrix.HomeserverEmulator.csproj", "{B30BD44B-AC06-485E-897E-8FFEE7352566}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Timings", "LibMatrix\ArcaneLibs\ArcaneLibs.Timings\ArcaneLibs.Timings.csproj", "{83C59CE2-02DC-487E-9DA3-0BD6C42BEA8A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.JsonSerializerContextGenerator", "LibMatrix\Utilities\LibMatrix.JsonSerializerContextGenerator\LibMatrix.JsonSerializerContextGenerator.csproj", "{B3B2EB3D-C339-4564-875D-3AE9FB777078}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.UsageTest", "LibMatrix\ArcaneLibs\ArcaneLibs.UsageTest\ArcaneLibs.UsageTest.csproj", "{867DBCFE-DEAF-4BA9-ADFB-2332DCA41769}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.TestDataGenerator", "LibMatrix\Utilities\LibMatrix.TestDataGenerator\LibMatrix.TestDataGenerator.csproj", "{94875851-1FD3-4651-961C-93B65B385A40}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.EventTypes", "LibMatrix\LibMatrix.EventTypes\LibMatrix.EventTypes.csproj", "{FD7B02E8-D366-4E18-A1DE-70AF23D4011C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Utilities.Bot", "LibMatrix\Utilities\LibMatrix.Utilities.Bot\LibMatrix.Utilities.Bot.csproj", "{9937801B-4E02-4E96-A03B-55F9A86680E3}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix", "LibMatrix\LibMatrix\LibMatrix.csproj", "{3F2751EA-DB1C-42D5-9A88-8A64B957CDA8}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ArcaneLibs", "ArcaneLibs", "{976B35D6-28DE-4084-84D9-B1FB0583D232}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{4990FD18-912E-41B8-B835-3932250CFF41}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs", "LibMatrix\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj", "{F926CAE9-5BEB-4581-9CB3-E8D314541AD9}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Tests", "LibMatrix\Tests\LibMatrix.Tests\LibMatrix.Tests.csproj", "{485FE478-7449-444D-959A-192D2E2D5F92}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Blazor.Components", "LibMatrix\ArcaneLibs\ArcaneLibs.Blazor.Components\ArcaneLibs.Blazor.Components.csproj", "{F7C159CB-B17B-448F-9D11-C9D6E835819F}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{1D56E9E7-928E-456C-969A-7BEE9A04D7A5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Legacy", "LibMatrix\ArcaneLibs\ArcaneLibs.Legacy\ArcaneLibs.Legacy.csproj", "{F1C722B7-CB68-4362-BC95-ADB948381C27}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.DebugDataValidationApi", "LibMatrix\Utilities\LibMatrix.DebugDataValidationApi\LibMatrix.DebugDataValidationApi.csproj", "{DBE28F38-2B7E-4F37-ABF9-51ACA18AFC3F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Logging", "LibMatrix\ArcaneLibs\ArcaneLibs.Logging\ArcaneLibs.Logging.csproj", "{51234361-799F-4D82-8741-377250882882}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.DevTestBot", "LibMatrix\Utilities\LibMatrix.DevTestBot\LibMatrix.DevTestBot.csproj", "{B0D835F1-F669-4249-8E7D-B6C039A3CB5B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.StringNormalisation", "LibMatrix\ArcaneLibs\ArcaneLibs.StringNormalisation\ArcaneLibs.StringNormalisation.csproj", "{21442B53-35F6-4858-BE8D-394E10E57587}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.E2eeTestKit", "LibMatrix\Utilities\LibMatrix.E2eeTestKit\LibMatrix.E2eeTestKit.csproj", "{A99D9740-7985-4985-9322-1B894EFDA550}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Timings", "LibMatrix\ArcaneLibs\ArcaneLibs.Timings\ArcaneLibs.Timings.csproj", "{F93A9EFE-5804-4EDD-9D7E-ED73C61158E2}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.HomeserverEmulator", "LibMatrix\Utilities\LibMatrix.HomeserverEmulator\LibMatrix.HomeserverEmulator.csproj", "{C7CA93D0-9D35-4C8A-A899-2E544FF1BEF6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.UsageTest", "LibMatrix\ArcaneLibs\ArcaneLibs.UsageTest\ArcaneLibs.UsageTest.csproj", "{B1C9CBFE-7939-4874-BF70-EF5B9C77D274}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.JsonSerializerContextGenerator", "LibMatrix\Utilities\LibMatrix.JsonSerializerContextGenerator\LibMatrix.JsonSerializerContextGenerator.csproj", "{48E82CDF-2ACB-4E2D-A121-A5FF454A053C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLib.Tests", "LibMatrix\ArcaneLibs\ArcaneLib.Tests\ArcaneLib.Tests.csproj", "{2F298242-2F55-4FD6-BDDE-C950326A61A7}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.TestDataGenerator", "LibMatrix\Utilities\LibMatrix.TestDataGenerator\LibMatrix.TestDataGenerator.csproj", "{B8C907F8-869C-4E2A-96BE-AC84E8E685FF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpamBot", "SpamBot\SpamBot.csproj", "{20EBD04C-A16C-4091-A1A5-D4AFECC4F609}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Utilities.Bot", "LibMatrix\Utilities\LibMatrix.Utilities.Bot\LibMatrix.Utilities.Bot.csproj", "{C5CFC0F0-8EC7-461C-A362-66146C4AEF03}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -59,104 +59,104 @@ Global {29F66829-AF62-4CEB-A05A-4D26A418180E}.Debug|Any CPU.Build.0 = Debug|Any CPU {29F66829-AF62-4CEB-A05A-4D26A418180E}.Release|Any CPU.ActiveCfg = Release|Any CPU {29F66829-AF62-4CEB-A05A-4D26A418180E}.Release|Any CPU.Build.0 = Release|Any CPU - {A8E886A0-D255-484E-9BC7-CD770E83F187}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A8E886A0-D255-484E-9BC7-CD770E83F187}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A8E886A0-D255-484E-9BC7-CD770E83F187}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A8E886A0-D255-484E-9BC7-CD770E83F187}.Release|Any CPU.Build.0 = Release|Any CPU - {02936216-5828-4574-B29C-0193AD4137FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {02936216-5828-4574-B29C-0193AD4137FE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {02936216-5828-4574-B29C-0193AD4137FE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {02936216-5828-4574-B29C-0193AD4137FE}.Release|Any CPU.Build.0 = Release|Any CPU - {A922F451-798F-47D7-BB28-ECEB6411FD82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A922F451-798F-47D7-BB28-ECEB6411FD82}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A922F451-798F-47D7-BB28-ECEB6411FD82}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A922F451-798F-47D7-BB28-ECEB6411FD82}.Release|Any CPU.Build.0 = Release|Any CPU - {EB49AC9D-44BE-438D-91D0-B5177A6AFD71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EB49AC9D-44BE-438D-91D0-B5177A6AFD71}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EB49AC9D-44BE-438D-91D0-B5177A6AFD71}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EB49AC9D-44BE-438D-91D0-B5177A6AFD71}.Release|Any CPU.Build.0 = Release|Any CPU - {17FC4683-6C73-4322-84CD-142D4EB4BB95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {17FC4683-6C73-4322-84CD-142D4EB4BB95}.Debug|Any CPU.Build.0 = Debug|Any CPU - {17FC4683-6C73-4322-84CD-142D4EB4BB95}.Release|Any CPU.ActiveCfg = Release|Any CPU - {17FC4683-6C73-4322-84CD-142D4EB4BB95}.Release|Any CPU.Build.0 = Release|Any CPU - {0AAB1D05-0779-44B2-A3E8-71F96DC42B71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0AAB1D05-0779-44B2-A3E8-71F96DC42B71}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0AAB1D05-0779-44B2-A3E8-71F96DC42B71}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0AAB1D05-0779-44B2-A3E8-71F96DC42B71}.Release|Any CPU.Build.0 = Release|Any CPU - {B30BD44B-AC06-485E-897E-8FFEE7352566}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B30BD44B-AC06-485E-897E-8FFEE7352566}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B30BD44B-AC06-485E-897E-8FFEE7352566}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B30BD44B-AC06-485E-897E-8FFEE7352566}.Release|Any CPU.Build.0 = Release|Any CPU - {B3B2EB3D-C339-4564-875D-3AE9FB777078}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B3B2EB3D-C339-4564-875D-3AE9FB777078}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B3B2EB3D-C339-4564-875D-3AE9FB777078}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B3B2EB3D-C339-4564-875D-3AE9FB777078}.Release|Any CPU.Build.0 = Release|Any CPU - {94875851-1FD3-4651-961C-93B65B385A40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {94875851-1FD3-4651-961C-93B65B385A40}.Debug|Any CPU.Build.0 = Debug|Any CPU - {94875851-1FD3-4651-961C-93B65B385A40}.Release|Any CPU.ActiveCfg = Release|Any CPU - {94875851-1FD3-4651-961C-93B65B385A40}.Release|Any CPU.Build.0 = Release|Any CPU - {9937801B-4E02-4E96-A03B-55F9A86680E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9937801B-4E02-4E96-A03B-55F9A86680E3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9937801B-4E02-4E96-A03B-55F9A86680E3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9937801B-4E02-4E96-A03B-55F9A86680E3}.Release|Any CPU.Build.0 = Release|Any CPU - {F926CAE9-5BEB-4581-9CB3-E8D314541AD9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F926CAE9-5BEB-4581-9CB3-E8D314541AD9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F926CAE9-5BEB-4581-9CB3-E8D314541AD9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F926CAE9-5BEB-4581-9CB3-E8D314541AD9}.Release|Any CPU.Build.0 = Release|Any CPU - {F7C159CB-B17B-448F-9D11-C9D6E835819F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F7C159CB-B17B-448F-9D11-C9D6E835819F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F7C159CB-B17B-448F-9D11-C9D6E835819F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F7C159CB-B17B-448F-9D11-C9D6E835819F}.Release|Any CPU.Build.0 = Release|Any CPU - {F1C722B7-CB68-4362-BC95-ADB948381C27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F1C722B7-CB68-4362-BC95-ADB948381C27}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F1C722B7-CB68-4362-BC95-ADB948381C27}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F1C722B7-CB68-4362-BC95-ADB948381C27}.Release|Any CPU.Build.0 = Release|Any CPU - {51234361-799F-4D82-8741-377250882882}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {51234361-799F-4D82-8741-377250882882}.Debug|Any CPU.Build.0 = Debug|Any CPU - {51234361-799F-4D82-8741-377250882882}.Release|Any CPU.ActiveCfg = Release|Any CPU - {51234361-799F-4D82-8741-377250882882}.Release|Any CPU.Build.0 = Release|Any CPU - {21442B53-35F6-4858-BE8D-394E10E57587}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {21442B53-35F6-4858-BE8D-394E10E57587}.Debug|Any CPU.Build.0 = Debug|Any CPU - {21442B53-35F6-4858-BE8D-394E10E57587}.Release|Any CPU.ActiveCfg = Release|Any CPU - {21442B53-35F6-4858-BE8D-394E10E57587}.Release|Any CPU.Build.0 = Release|Any CPU - {F93A9EFE-5804-4EDD-9D7E-ED73C61158E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F93A9EFE-5804-4EDD-9D7E-ED73C61158E2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F93A9EFE-5804-4EDD-9D7E-ED73C61158E2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F93A9EFE-5804-4EDD-9D7E-ED73C61158E2}.Release|Any CPU.Build.0 = Release|Any CPU - {B1C9CBFE-7939-4874-BF70-EF5B9C77D274}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B1C9CBFE-7939-4874-BF70-EF5B9C77D274}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B1C9CBFE-7939-4874-BF70-EF5B9C77D274}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B1C9CBFE-7939-4874-BF70-EF5B9C77D274}.Release|Any CPU.Build.0 = Release|Any CPU - {2F298242-2F55-4FD6-BDDE-C950326A61A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2F298242-2F55-4FD6-BDDE-C950326A61A7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2F298242-2F55-4FD6-BDDE-C950326A61A7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2F298242-2F55-4FD6-BDDE-C950326A61A7}.Release|Any CPU.Build.0 = Release|Any CPU {20EBD04C-A16C-4091-A1A5-D4AFECC4F609}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {20EBD04C-A16C-4091-A1A5-D4AFECC4F609}.Debug|Any CPU.Build.0 = Debug|Any CPU {20EBD04C-A16C-4091-A1A5-D4AFECC4F609}.Release|Any CPU.ActiveCfg = Release|Any CPU {20EBD04C-A16C-4091-A1A5-D4AFECC4F609}.Release|Any CPU.Build.0 = Release|Any CPU + {ABBBED50-427C-4B66-B392-AD0DFDFD4C03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ABBBED50-427C-4B66-B392-AD0DFDFD4C03}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ABBBED50-427C-4B66-B392-AD0DFDFD4C03}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ABBBED50-427C-4B66-B392-AD0DFDFD4C03}.Release|Any CPU.Build.0 = Release|Any CPU + {BAED1DD7-05B6-49C3-B589-E647FF4E3D87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BAED1DD7-05B6-49C3-B589-E647FF4E3D87}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BAED1DD7-05B6-49C3-B589-E647FF4E3D87}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BAED1DD7-05B6-49C3-B589-E647FF4E3D87}.Release|Any CPU.Build.0 = Release|Any CPU + {EBEF521F-6535-41ED-9133-359040BE3EB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EBEF521F-6535-41ED-9133-359040BE3EB7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EBEF521F-6535-41ED-9133-359040BE3EB7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EBEF521F-6535-41ED-9133-359040BE3EB7}.Release|Any CPU.Build.0 = Release|Any CPU + {DA78C7E6-FFAD-48F8-A006-AEEB9CDDF336}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA78C7E6-FFAD-48F8-A006-AEEB9CDDF336}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA78C7E6-FFAD-48F8-A006-AEEB9CDDF336}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA78C7E6-FFAD-48F8-A006-AEEB9CDDF336}.Release|Any CPU.Build.0 = Release|Any CPU + {377E9844-FCC2-44A1-954B-35CB210760E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {377E9844-FCC2-44A1-954B-35CB210760E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {377E9844-FCC2-44A1-954B-35CB210760E2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {377E9844-FCC2-44A1-954B-35CB210760E2}.Release|Any CPU.Build.0 = Release|Any CPU + {A45A9C69-36E8-4EA4-B362-AE23A5EB75D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A45A9C69-36E8-4EA4-B362-AE23A5EB75D1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A45A9C69-36E8-4EA4-B362-AE23A5EB75D1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A45A9C69-36E8-4EA4-B362-AE23A5EB75D1}.Release|Any CPU.Build.0 = Release|Any CPU + {83C59CE2-02DC-487E-9DA3-0BD6C42BEA8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {83C59CE2-02DC-487E-9DA3-0BD6C42BEA8A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {83C59CE2-02DC-487E-9DA3-0BD6C42BEA8A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {83C59CE2-02DC-487E-9DA3-0BD6C42BEA8A}.Release|Any CPU.Build.0 = Release|Any CPU + {867DBCFE-DEAF-4BA9-ADFB-2332DCA41769}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {867DBCFE-DEAF-4BA9-ADFB-2332DCA41769}.Debug|Any CPU.Build.0 = Debug|Any CPU + {867DBCFE-DEAF-4BA9-ADFB-2332DCA41769}.Release|Any CPU.ActiveCfg = Release|Any CPU + {867DBCFE-DEAF-4BA9-ADFB-2332DCA41769}.Release|Any CPU.Build.0 = Release|Any CPU + {FD7B02E8-D366-4E18-A1DE-70AF23D4011C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FD7B02E8-D366-4E18-A1DE-70AF23D4011C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FD7B02E8-D366-4E18-A1DE-70AF23D4011C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FD7B02E8-D366-4E18-A1DE-70AF23D4011C}.Release|Any CPU.Build.0 = Release|Any CPU + {3F2751EA-DB1C-42D5-9A88-8A64B957CDA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F2751EA-DB1C-42D5-9A88-8A64B957CDA8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F2751EA-DB1C-42D5-9A88-8A64B957CDA8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F2751EA-DB1C-42D5-9A88-8A64B957CDA8}.Release|Any CPU.Build.0 = Release|Any CPU + {485FE478-7449-444D-959A-192D2E2D5F92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {485FE478-7449-444D-959A-192D2E2D5F92}.Debug|Any CPU.Build.0 = Debug|Any CPU + {485FE478-7449-444D-959A-192D2E2D5F92}.Release|Any CPU.ActiveCfg = Release|Any CPU + {485FE478-7449-444D-959A-192D2E2D5F92}.Release|Any CPU.Build.0 = Release|Any CPU + {DBE28F38-2B7E-4F37-ABF9-51ACA18AFC3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DBE28F38-2B7E-4F37-ABF9-51ACA18AFC3F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DBE28F38-2B7E-4F37-ABF9-51ACA18AFC3F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DBE28F38-2B7E-4F37-ABF9-51ACA18AFC3F}.Release|Any CPU.Build.0 = Release|Any CPU + {B0D835F1-F669-4249-8E7D-B6C039A3CB5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B0D835F1-F669-4249-8E7D-B6C039A3CB5B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B0D835F1-F669-4249-8E7D-B6C039A3CB5B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B0D835F1-F669-4249-8E7D-B6C039A3CB5B}.Release|Any CPU.Build.0 = Release|Any CPU + {A99D9740-7985-4985-9322-1B894EFDA550}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A99D9740-7985-4985-9322-1B894EFDA550}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A99D9740-7985-4985-9322-1B894EFDA550}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A99D9740-7985-4985-9322-1B894EFDA550}.Release|Any CPU.Build.0 = Release|Any CPU + {C7CA93D0-9D35-4C8A-A899-2E544FF1BEF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C7CA93D0-9D35-4C8A-A899-2E544FF1BEF6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C7CA93D0-9D35-4C8A-A899-2E544FF1BEF6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C7CA93D0-9D35-4C8A-A899-2E544FF1BEF6}.Release|Any CPU.Build.0 = Release|Any CPU + {48E82CDF-2ACB-4E2D-A121-A5FF454A053C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48E82CDF-2ACB-4E2D-A121-A5FF454A053C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48E82CDF-2ACB-4E2D-A121-A5FF454A053C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48E82CDF-2ACB-4E2D-A121-A5FF454A053C}.Release|Any CPU.Build.0 = Release|Any CPU + {B8C907F8-869C-4E2A-96BE-AC84E8E685FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B8C907F8-869C-4E2A-96BE-AC84E8E685FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B8C907F8-869C-4E2A-96BE-AC84E8E685FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B8C907F8-869C-4E2A-96BE-AC84E8E685FF}.Release|Any CPU.Build.0 = Release|Any CPU + {C5CFC0F0-8EC7-461C-A362-66146C4AEF03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C5CFC0F0-8EC7-461C-A362-66146C4AEF03}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C5CFC0F0-8EC7-461C-A362-66146C4AEF03}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C5CFC0F0-8EC7-461C-A362-66146C4AEF03}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution - {A8E886A0-D255-484E-9BC7-CD770E83F187} = {FC63408D-5EBC-44D0-9199-AA6359D9ACD9} - {02936216-5828-4574-B29C-0193AD4137FE} = {FC63408D-5EBC-44D0-9199-AA6359D9ACD9} - {35972DDC-EF9B-4002-80B8-92081E746B2D} = {FC63408D-5EBC-44D0-9199-AA6359D9ACD9} - {A922F451-798F-47D7-BB28-ECEB6411FD82} = {35972DDC-EF9B-4002-80B8-92081E746B2D} - {1C1B86CD-115F-4673-84AE-04EECB916BDA} = {FC63408D-5EBC-44D0-9199-AA6359D9ACD9} - {EB49AC9D-44BE-438D-91D0-B5177A6AFD71} = {1C1B86CD-115F-4673-84AE-04EECB916BDA} - {17FC4683-6C73-4322-84CD-142D4EB4BB95} = {1C1B86CD-115F-4673-84AE-04EECB916BDA} - {0AAB1D05-0779-44B2-A3E8-71F96DC42B71} = {1C1B86CD-115F-4673-84AE-04EECB916BDA} - {B30BD44B-AC06-485E-897E-8FFEE7352566} = {1C1B86CD-115F-4673-84AE-04EECB916BDA} - {B3B2EB3D-C339-4564-875D-3AE9FB777078} = {1C1B86CD-115F-4673-84AE-04EECB916BDA} - {94875851-1FD3-4651-961C-93B65B385A40} = {1C1B86CD-115F-4673-84AE-04EECB916BDA} - {9937801B-4E02-4E96-A03B-55F9A86680E3} = {1C1B86CD-115F-4673-84AE-04EECB916BDA} {976B35D6-28DE-4084-84D9-B1FB0583D232} = {FC63408D-5EBC-44D0-9199-AA6359D9ACD9} - {F926CAE9-5BEB-4581-9CB3-E8D314541AD9} = {976B35D6-28DE-4084-84D9-B1FB0583D232} - {F7C159CB-B17B-448F-9D11-C9D6E835819F} = {976B35D6-28DE-4084-84D9-B1FB0583D232} - {F1C722B7-CB68-4362-BC95-ADB948381C27} = {976B35D6-28DE-4084-84D9-B1FB0583D232} - {51234361-799F-4D82-8741-377250882882} = {976B35D6-28DE-4084-84D9-B1FB0583D232} - {21442B53-35F6-4858-BE8D-394E10E57587} = {976B35D6-28DE-4084-84D9-B1FB0583D232} - {F93A9EFE-5804-4EDD-9D7E-ED73C61158E2} = {976B35D6-28DE-4084-84D9-B1FB0583D232} - {B1C9CBFE-7939-4874-BF70-EF5B9C77D274} = {976B35D6-28DE-4084-84D9-B1FB0583D232} - {2F298242-2F55-4FD6-BDDE-C950326A61A7} = {976B35D6-28DE-4084-84D9-B1FB0583D232} + {ABBBED50-427C-4B66-B392-AD0DFDFD4C03} = {976B35D6-28DE-4084-84D9-B1FB0583D232} + {BAED1DD7-05B6-49C3-B589-E647FF4E3D87} = {976B35D6-28DE-4084-84D9-B1FB0583D232} + {EBEF521F-6535-41ED-9133-359040BE3EB7} = {976B35D6-28DE-4084-84D9-B1FB0583D232} + {DA78C7E6-FFAD-48F8-A006-AEEB9CDDF336} = {976B35D6-28DE-4084-84D9-B1FB0583D232} + {377E9844-FCC2-44A1-954B-35CB210760E2} = {976B35D6-28DE-4084-84D9-B1FB0583D232} + {A45A9C69-36E8-4EA4-B362-AE23A5EB75D1} = {976B35D6-28DE-4084-84D9-B1FB0583D232} + {83C59CE2-02DC-487E-9DA3-0BD6C42BEA8A} = {976B35D6-28DE-4084-84D9-B1FB0583D232} + {867DBCFE-DEAF-4BA9-ADFB-2332DCA41769} = {976B35D6-28DE-4084-84D9-B1FB0583D232} + {FD7B02E8-D366-4E18-A1DE-70AF23D4011C} = {FC63408D-5EBC-44D0-9199-AA6359D9ACD9} + {3F2751EA-DB1C-42D5-9A88-8A64B957CDA8} = {FC63408D-5EBC-44D0-9199-AA6359D9ACD9} + {4990FD18-912E-41B8-B835-3932250CFF41} = {FC63408D-5EBC-44D0-9199-AA6359D9ACD9} + {485FE478-7449-444D-959A-192D2E2D5F92} = {4990FD18-912E-41B8-B835-3932250CFF41} + {1D56E9E7-928E-456C-969A-7BEE9A04D7A5} = {FC63408D-5EBC-44D0-9199-AA6359D9ACD9} + {DBE28F38-2B7E-4F37-ABF9-51ACA18AFC3F} = {1D56E9E7-928E-456C-969A-7BEE9A04D7A5} + {B0D835F1-F669-4249-8E7D-B6C039A3CB5B} = {1D56E9E7-928E-456C-969A-7BEE9A04D7A5} + {A99D9740-7985-4985-9322-1B894EFDA550} = {1D56E9E7-928E-456C-969A-7BEE9A04D7A5} + {C7CA93D0-9D35-4C8A-A899-2E544FF1BEF6} = {1D56E9E7-928E-456C-969A-7BEE9A04D7A5} + {48E82CDF-2ACB-4E2D-A121-A5FF454A053C} = {1D56E9E7-928E-456C-969A-7BEE9A04D7A5} + {B8C907F8-869C-4E2A-96BE-AC84E8E685FF} = {1D56E9E7-928E-456C-969A-7BEE9A04D7A5} + {C5CFC0F0-8EC7-461C-A362-66146C4AEF03} = {1D56E9E7-928E-456C-969A-7BEE9A04D7A5} EndGlobalSection EndGlobal diff --git a/MatrixContentFilter/Abstractions/IContentFilter.cs b/MatrixContentFilter/Abstractions/IContentFilter.cs
index 108030b..8bd01ff 100644 --- a/MatrixContentFilter/Abstractions/IContentFilter.cs +++ b/MatrixContentFilter/Abstractions/IContentFilter.cs
@@ -1,12 +1,15 @@ using System.Diagnostics; using LibMatrix; using LibMatrix.Responses; -using MatrixContentFilter.EventTypes; namespace MatrixContentFilter.Abstractions; -public abstract class IContentFilter -{ +public abstract class IContentFilter { + private protected abstract string GetFilterName(); + private protected abstract string GetEventTypeName(); + private protected abstract string? GetEventSubtypeName(); + private protected abstract string? GetMetricsPrefix(); + public virtual Task ProcessSyncAsync(SyncResponse syncEvent) { var type = this.GetType().FullName; Console.WriteLine($"WARNING: {type} does not implement ProcessSyncAsync(SyncResponse syncEvent)"); @@ -24,4 +27,6 @@ public abstract class IContentFilter } public int ActionCount { get; set; } = 0; + + } \ No newline at end of file diff --git a/MatrixContentFilter/Commands/BanCommand.cs b/MatrixContentFilter/Commands/BanCommand.cs new file mode 100644
index 0000000..0ae38e2 --- /dev/null +++ b/MatrixContentFilter/Commands/BanCommand.cs
@@ -0,0 +1,97 @@ +using LibMatrix.Helpers; +using LibMatrix.RoomTypes; +using LibMatrix.Utilities.Bot.Interfaces; +using MatrixContentFilter.Services; +using MatrixContentFilter.Services.AsyncActionQueues; + +namespace MatrixContentFilter.Commands; + +public class BanCommand( + ConfigurationService filterConfigService, + AsyncMessageQueue msgQueue, + InfoCacheService infoCache, + ConfigurationService cfgService, + AbstractAsyncActionQueue actionQueue +) : ICommand { + public string Name { get; } = "ban"; + public string[]? Aliases { get; } = []; + public string Description { get; } = "Ban user (see --help)"; + public bool Unlisted { get; } = false; + + public async Task Invoke(CommandContext ctx) { + bool HasMoreArgs(int index) => ctx.Args.Length > index + 1; + if (ctx.Args.Contains("--help") || ctx.Args.Length == 0) { + await SendHelp(ctx.Room); + return; + } + + BanCommandArgs args = new() { + UserId = null, + Reason = null + }; + + // if (ctx.Args.Contains("--redact")) { + // var index = Array.IndexOf(ctx.Args, "--redact"); + // if (HasMoreArgs(index)) { + // args.Redact = int.TryParse(ctx.Args[index + 1], out var redact) ? redact : 500; + // } + // } + + for (int i = 0; i < ctx.Args.Length; i++) { + if (ctx.Args[i].StartsWith("--")) { + switch (ctx.Args[i]) { + case "--redact": { + if (HasMoreArgs(i)) { + // args.Redact = int.TryParse(ctx.Args[i + 1], out var redact) ? redact : 500; + if (int.TryParse(ctx.Args[i + 1], out var redact)) { + args.Redact = redact; + i++; + } + else args.Redact = 500; + } + + break; + } + } + } + else if (args.UserId == null) { + args.UserId = ctx.Args[i]; + } + else args.Reason += ctx.Args[i] + " "; + } + } + + private async Task SendHelp(GenericRoom room) { + string[][] helpTable = [ + ["--help", "", "Show this message"], + ["--redact", "count ?? 500", "Redact last N events from user"], + ["user_id", "required", "User ID to ban"], + ["reason", "required", "Reason for ban"], + ]; + var msb = new MessageBuilder("m.notice") + .WithTable(tb => { + tb.WithTitle("Help for ban command", 3) + .WithTitle("Basic usage: ban [options] user_id reason", 3) + .WithRow(rb => + rb.WithCell("Command") + .WithCell("Arguments") + .WithCell("Description") + ); + foreach (var row in helpTable) { + tb.WithRow(rb => + rb.WithCell(row[0]) + .WithCell(row[1]) + .WithCell(row[2]) + ); + } + }); + + await room.SendMessageEventAsync(msb.Build()); + } + + private struct BanCommandArgs { + public string UserId { get; set; } + public string? Reason { get; set; } + public int? Redact { get; set; } + } +} \ No newline at end of file diff --git a/MatrixContentFilter/Commands/CheckHistoryCommand.cs b/MatrixContentFilter/Commands/CheckHistoryCommand.cs
index 1e98545..4d34ee6 100644 --- a/MatrixContentFilter/Commands/CheckHistoryCommand.cs +++ b/MatrixContentFilter/Commands/CheckHistoryCommand.cs
@@ -1,9 +1,7 @@ using LibMatrix.Helpers; -using LibMatrix.Homeservers; using LibMatrix.Utilities.Bot.Interfaces; using MatrixContentFilter.Abstractions; using MatrixContentFilter.Services; -using Microsoft.Extensions.Logging; namespace MatrixContentFilter.Commands; diff --git a/MatrixContentFilter/Commands/ConfigureCommand.cs b/MatrixContentFilter/Commands/ConfigureCommand.cs
index 756fc14..472e387 100644 --- a/MatrixContentFilter/Commands/ConfigureCommand.cs +++ b/MatrixContentFilter/Commands/ConfigureCommand.cs
@@ -1,4 +1,3 @@ -using System.Text; using LibMatrix.EventTypes.Spec; using LibMatrix.Utilities.Bot.Commands; using LibMatrix.Utilities.Bot.Interfaces; diff --git a/MatrixContentFilter/Commands/DumpEventCommand.cs b/MatrixContentFilter/Commands/DumpEventCommand.cs
index 5131e19..ebffcd4 100644 --- a/MatrixContentFilter/Commands/DumpEventCommand.cs +++ b/MatrixContentFilter/Commands/DumpEventCommand.cs
@@ -1,14 +1,8 @@ using ArcaneLibs.Extensions; -using LibMatrix.EventTypes.Spec; -using LibMatrix.EventTypes.Spec.State; -using LibMatrix.Filters; using LibMatrix.Helpers; -using LibMatrix.Homeservers; using LibMatrix.Utilities.Bot.Interfaces; -using MatrixContentFilter.Abstractions; using MatrixContentFilter.Services; using MatrixContentFilter.Services.AsyncActionQueues; -using Microsoft.Extensions.Logging; namespace MatrixContentFilter.Commands; diff --git a/MatrixContentFilter/Commands/GetConfigCommand.cs b/MatrixContentFilter/Commands/GetConfigCommand.cs
index bac00ca..ebd059c 100644 --- a/MatrixContentFilter/Commands/GetConfigCommand.cs +++ b/MatrixContentFilter/Commands/GetConfigCommand.cs
@@ -1,12 +1,8 @@ -using System.Text; using ArcaneLibs.Attributes; using ArcaneLibs.Extensions; -using LibMatrix.EventTypes.Spec; using LibMatrix.Helpers; -using LibMatrix.Utilities.Bot.Commands; using LibMatrix.Utilities.Bot.Interfaces; using MatrixContentFilter.EventTypes; -using Microsoft.Extensions.DependencyInjection; namespace MatrixContentFilter.Commands; diff --git a/MatrixContentFilter/Commands/NewRoomCommand.cs b/MatrixContentFilter/Commands/NewRoomCommand.cs
index b8becd4..916649e 100644 --- a/MatrixContentFilter/Commands/NewRoomCommand.cs +++ b/MatrixContentFilter/Commands/NewRoomCommand.cs
@@ -1,12 +1,4 @@ -using System.Text; -using ArcaneLibs.Attributes; -using ArcaneLibs.Extensions; -using LibMatrix.EventTypes.Spec; -using LibMatrix.Helpers; -using LibMatrix.Utilities.Bot.Commands; using LibMatrix.Utilities.Bot.Interfaces; -using MatrixContentFilter.EventTypes; -using Microsoft.Extensions.DependencyInjection; namespace MatrixContentFilter.Commands; diff --git a/MatrixContentFilter/Commands/RedactCommand.cs b/MatrixContentFilter/Commands/RedactCommand.cs
index 6b2f8b6..9d45f18 100644 --- a/MatrixContentFilter/Commands/RedactCommand.cs +++ b/MatrixContentFilter/Commands/RedactCommand.cs
@@ -1,14 +1,11 @@ using ArcaneLibs.Extensions; using LibMatrix.EventTypes.Spec; -using LibMatrix.EventTypes.Spec.State; +using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Filters; using LibMatrix.Helpers; -using LibMatrix.Homeservers; using LibMatrix.Utilities.Bot.Interfaces; -using MatrixContentFilter.Abstractions; using MatrixContentFilter.Services; using MatrixContentFilter.Services.AsyncActionQueues; -using Microsoft.Extensions.Logging; namespace MatrixContentFilter.Commands; @@ -54,11 +51,11 @@ public class RedactCommand( Key = "\u23f3" //hour glass emoji } }); - - await foreach (var resp in ctx.Room.GetManyMessagesAsync(limit: count, chunkSize: Math.Min(count, 250) - ,filter: new SyncFilter.RoomFilter.StateFilter(types: [RoomMemberEventContent.EventId, RoomMessageEventContent.EventId], senders: [mxid]) + + await foreach (var resp in ctx.Room.GetManyMessagesAsync(limit: count, chunkSize: Math.Min(count, 250), + filter: new SyncFilter.RoomFilter.StateFilter(types: [RoomMemberEventContent.EventId, RoomMessageEventContent.EventId], senders: [mxid]) .ToJson(indent: false, ignoreNull: true).UrlEncode()) - ) { + ) { foreach (var msg in resp.Chunk) { if (msg.Sender != mxid) continue; if (msg is not { Type: RoomMemberEventContent.EventId or RoomMessageEventContent.EventId }) continue; @@ -85,7 +82,7 @@ public class RedactCommand( .Build()); }); } - + await ctx.Room.RedactEventAsync(hourglassReaction.EventId); await ctx.Room.SendTimelineEventAsync("m.reaction", new RoomMessageReactionEventContent() { RelatesTo = new() { diff --git a/MatrixContentFilter/EventTypes/FilterConfiguration.cs b/MatrixContentFilter/EventTypes/FilterConfiguration.cs
index 8e3b900..78e164a 100644 --- a/MatrixContentFilter/EventTypes/FilterConfiguration.cs +++ b/MatrixContentFilter/EventTypes/FilterConfiguration.cs
@@ -28,11 +28,26 @@ public class FilterConfiguration : EventContent { [FriendlyName(Name = "Links")] public BasicFilterConfiguration? UrlFilter { get; set; } + [JsonPropertyName("invite_limiter")] + [FriendlyName(Name = "Pending invite limits")] + public BasicLimiterConfiguration? InviteLimiter { get; set; } + [JsonPropertyName("ignored_users")] [FriendlyName(Name = "Ignored Users")] public List<string>? IgnoredUsers { get; set; } - - + + + public class BasicLimiterConfiguration { + [JsonPropertyName("heuristic_trip_point")] + public int? HeuristicTripPoint { get; set; } + + [JsonPropertyName("max_count")] + public int? MaxCount { get; set; } + + [JsonPropertyName("ignored_users")] + public List<string>? IgnoredUsers { get; set; } + } + public class BasicFilterConfiguration { [JsonPropertyName("allowed")] public bool? Allowed { get; set; } diff --git a/MatrixContentFilter/Handlers/Filters/Generic/BasicMessageTypeFilter.cs b/MatrixContentFilter/Handlers/Filters/Generic/BasicMessageTypeFilter.cs new file mode 100644
index 0000000..2a9c674 --- /dev/null +++ b/MatrixContentFilter/Handlers/Filters/Generic/BasicMessageTypeFilter.cs
@@ -0,0 +1,88 @@ +using ArcaneLibs.Extensions; +using LibMatrix; +using LibMatrix.EventTypes.Spec; +using LibMatrix.Helpers; +using LibMatrix.Homeservers; +using LibMatrix.Responses; +using LibMatrix.RoomTypes; +using MatrixContentFilter.Abstractions; +using MatrixContentFilter.EventTypes; +using MatrixContentFilter.Services; +using MatrixContentFilter.Services.AsyncActionQueues; + +namespace MatrixContentFilter.Handlers.Filters.Generic; + +public abstract class BasicMessageTypeFilter( + ConfigurationService cfgService, + AuthenticatedHomeserverGeneric hs, + AsyncMessageQueue msgQueue, + InfoCacheService infoCache, + AbstractAsyncActionQueue actionQueue, + MatrixContentFilterMetrics metrics) + : IContentFilter { + private protected string MessageType = null!; + private protected string MessageTypeName = null!; + private protected string MessageTypeNamePlural = null!; + + public override Task ProcessSyncAsync(SyncResponse syncResponse) { + if (syncResponse.Rooms?.Join is null) return Task.CompletedTask; + var tasks = syncResponse.Rooms.Join.Select(ProcessRoomAsync); + return Task.WhenAll(tasks); + } + + private Task ProcessRoomAsync(KeyValuePair<string, SyncResponse.RoomsDataStructure.JoinedRoomDataStructure> syncRoom) { + var (roomId, roomData) = syncRoom; + if (roomId == cfgService.LogRoom.RoomId || roomId == cfgService.ControlRoom.RoomId) return Task.CompletedTask; + if (roomData.Timeline?.Events is null) return Task.CompletedTask; + var config = cfgService.RoomConfigurationOverrides.GetValueOrDefault(roomId)?.ImageFilter; + + var room = hs.GetRoom(roomId); + + var tasks = roomData.Timeline.Events.Select(msg => ProcessEventAsync(room, msg, config)); + return Task.WhenAll(tasks); + } + + public override Task ProcessEventListAsync(List<StateEventResponse> events) { + var tasks = events.GroupBy(x => x.RoomId).Select(async x => { + var room = hs.GetRoom(x.Key); + var config = cfgService.RoomConfigurationOverrides.GetValueOrDefault(x.Key)?.ImageFilter; + var tasks = x.Select(msg => ProcessEventAsync(room, msg, config)).ToList(); + await Task.WhenAll(tasks); + }); + + return Task.WhenAll(tasks); + } + + private async Task ProcessEventAsync(GenericRoom room, StateEventResponse msg, FilterConfiguration.BasicFilterConfiguration roomConfiguration) { + if (msg.Type != "m.room.message") return; + var content = msg.TypedContent as RoomMessageEventContent; + if (content?.MessageType != MessageType) return; + metrics.Increment(MessageTypeName + "_filter_processed_event_count"); + + await actionQueue.EqueueActionAsync(msg.EventId, async () => { + while (true) { + try { + Console.WriteLine("Redacting {0} message: {1}", MessageTypeName, msg.EventId); + await room.RedactEventAsync(msg.EventId ?? throw new ArgumentException("Event ID is null?"), $"Not allowed to send {MessageTypeNamePlural} in this room!"); + break; + } + catch (Exception e) { + msgQueue.EnqueueMessageAsync(cfgService.LogRoom, new MessageBuilder("m.notice") + .WithBody($"Error redacting {MessageTypeName} message in {room.RoomId}!") + .WithCollapsibleSection("Error data", msb => msb.WithCodeBlock(e.ToString(), "csharp")) + .Build()); + } + } + + var displayName = await infoCache.GetDisplayNameAsync(room.RoomId, msg.Sender); + var roomName = await infoCache.GetRoomNameAsync(room.RoomId); + + msgQueue.EnqueueMessageAsync(cfgService.LogRoom, new MessageBuilder("m.notice") + .WithBody($"{MessageTypeName[0].ToString().ToUpper() + MessageTypeName[1..]} sent by ").WithMention(msg.Sender, displayName).WithBody(" in ") + .WithMention(room.RoomId, roomName).WithBody(" was removed!").WithNewline() + .WithCollapsibleSection("Message data", msb => msb.WithCodeBlock(content.ToJson(ignoreNull: true), "json")) + .Build()); + }); + ActionCount++; + } +} \ No newline at end of file diff --git a/MatrixContentFilter/Handlers/Filters/Generic/GenericEventTypeFilter.cs b/MatrixContentFilter/Handlers/Filters/Generic/GenericEventTypeFilter.cs new file mode 100644
index 0000000..64e4cff --- /dev/null +++ b/MatrixContentFilter/Handlers/Filters/Generic/GenericEventTypeFilter.cs
@@ -0,0 +1,86 @@ +using ArcaneLibs.Extensions; +using LibMatrix; +using LibMatrix.EventTypes.Spec; +using LibMatrix.Helpers; +using LibMatrix.Homeservers; +using LibMatrix.Responses; +using LibMatrix.RoomTypes; +using MatrixContentFilter.Abstractions; +using MatrixContentFilter.EventTypes; +using MatrixContentFilter.Services; +using MatrixContentFilter.Services.AsyncActionQueues; + +namespace MatrixContentFilter.Handlers.Filters.Generic; + +public abstract class GenericEventTypeFilter( + ConfigurationService cfgService, + AuthenticatedHomeserverGeneric hs, + AsyncMessageQueue msgQueue, + InfoCacheService infoCache, + AbstractAsyncActionQueue actionQueue, + MatrixContentFilterMetrics metrics) + : IContentFilter { + private protected abstract string? GetEventType(); + + public override Task ProcessSyncAsync(SyncResponse syncResponse) { + if (syncResponse.Rooms?.Join is null) return Task.CompletedTask; + var tasks = syncResponse.Rooms.Join.Select(ProcessRoomAsync); + return Task.WhenAll(tasks); + } + + private Task ProcessRoomAsync(KeyValuePair<string, SyncResponse.RoomsDataStructure.JoinedRoomDataStructure> syncRoom) { + var (roomId, roomData) = syncRoom; + if (roomId == cfgService.LogRoom.RoomId || roomId == cfgService.ControlRoom.RoomId) return Task.CompletedTask; + if (roomData.Timeline?.Events is null) return Task.CompletedTask; + var config = cfgService.RoomConfigurationOverrides.GetValueOrDefault(roomId)?.ImageFilter; + + var room = hs.GetRoom(roomId); + + var tasks = roomData.Timeline.Events.Select(msg => ProcessEventAsync(room, msg, config)); + return Task.WhenAll(tasks); + } + + public override Task ProcessEventListAsync(List<StateEventResponse> events) { + var tasks = events.GroupBy(x => x.RoomId).Select(async x => { + var room = hs.GetRoom(x.Key); + var config = cfgService.RoomConfigurationOverrides.GetValueOrDefault(x.Key)?.ImageFilter; + var tasks = x.Select(msg => ProcessEventAsync(room, msg, config)).ToList(); + await Task.WhenAll(tasks); + }); + + return Task.WhenAll(tasks); + } + + private async Task ProcessEventAsync(GenericRoom room, StateEventResponse msg, FilterConfiguration.BasicFilterConfiguration roomConfiguration) { + if (msg.Type != "m.room.message") return; + var content = msg.TypedContent as RoomMessageEventContent; + if (content?.MessageType != GetEventType()) return; + metrics.Increment(GetMetricsPrefix() + "_filter_processed_event_count"); + + await actionQueue.EqueueActionAsync(msg.EventId, async () => { + while (true) { + try { + Console.WriteLine("Redacting {0} message: {1}", MessageTypeName, msg.EventId); + await room.RedactEventAsync(msg.EventId ?? throw new ArgumentException("Event ID is null?"), $"Not allowed to send {MessageTypeNamePlural} in this room!"); + break; + } + catch (Exception e) { + msgQueue.EnqueueMessageAsync(cfgService.LogRoom, new MessageBuilder("m.notice") + .WithBody($"Error redacting {MessageTypeName} message in {room.RoomId}!") + .WithCollapsibleSection("Error data", msb => msb.WithCodeBlock(e.ToString(), "csharp")) + .Build()); + } + } + + var displayName = await infoCache.GetDisplayNameAsync(room.RoomId, msg.Sender); + var roomName = await infoCache.GetRoomNameAsync(room.RoomId); + + msgQueue.EnqueueMessageAsync(cfgService.LogRoom, new MessageBuilder("m.notice") + .WithBody($"{MessageTypeName[0].ToString().ToUpper() + MessageTypeName[1..]} sent by ").WithMention(msg.Sender, displayName).WithBody(" in ") + .WithMention(room.RoomId, roomName).WithBody(" was removed!").WithNewline() + .WithCollapsibleSection("Message data", msb => msb.WithCodeBlock(content.ToJson(ignoreNull: true), "json")) + .Build()); + }); + ActionCount++; + } +} \ No newline at end of file diff --git a/MatrixContentFilter/Handlers/Filters/ImageFilter.cs b/MatrixContentFilter/Handlers/Filters/ImageFilter.cs
index 13a68f9..7a298ce 100644 --- a/MatrixContentFilter/Handlers/Filters/ImageFilter.cs +++ b/MatrixContentFilter/Handlers/Filters/ImageFilter.cs
@@ -1,7 +1,3 @@ -using System.Runtime.Loader; -using System.Security.Cryptography; -using System.Text.Json.Nodes; -using ArcaneLibs.Collections; using ArcaneLibs.Extensions; using LibMatrix; using LibMatrix.EventTypes.Spec; @@ -21,46 +17,43 @@ public class ImageFilter( AuthenticatedHomeserverGeneric hs, AsyncMessageQueue msgQueue, InfoCacheService infoCache, - AbstractAsyncActionQueue actionQueue) + AbstractAsyncActionQueue actionQueue, + MatrixContentFilterMetrics metrics) : IContentFilter { - public override async Task ProcessSyncAsync(SyncResponse syncResponse) { - Console.WriteLine("Processing image filter"); - if (syncResponse.Rooms?.Join is null) return; + public override Task ProcessSyncAsync(SyncResponse syncResponse) { + if (syncResponse.Rooms?.Join is null) return Task.CompletedTask; var tasks = syncResponse.Rooms.Join.Select(ProcessRoomAsync); - await Task.WhenAll(tasks); + return Task.WhenAll(tasks); } - // private SemaphoreSlim semaphore = new(8, 8); - - private async Task ProcessRoomAsync(KeyValuePair<string, SyncResponse.RoomsDataStructure.JoinedRoomDataStructure> syncRoom) { + private Task ProcessRoomAsync(KeyValuePair<string, SyncResponse.RoomsDataStructure.JoinedRoomDataStructure> syncRoom) { var (roomId, roomData) = syncRoom; - if (roomId == cfgService.LogRoom.RoomId || roomId == cfgService.ControlRoom.RoomId) return; - if (roomData.Timeline?.Events is null) return; + if (roomId == cfgService.LogRoom.RoomId || roomId == cfgService.ControlRoom.RoomId) return Task.CompletedTask; + if (roomData.Timeline?.Events is null) return Task.CompletedTask; var config = cfgService.RoomConfigurationOverrides.GetValueOrDefault(roomId)?.ImageFilter; var room = hs.GetRoom(roomId); var tasks = roomData.Timeline.Events.Select(msg => ProcessEventAsync(room, msg, config)); - await Task.WhenAll(tasks); + return Task.WhenAll(tasks); } - public override async Task ProcessEventListAsync(List<StateEventResponse> events) { + public override Task ProcessEventListAsync(List<StateEventResponse> events) { var tasks = events.GroupBy(x => x.RoomId).Select(async x => { var room = hs.GetRoom(x.Key); var config = cfgService.RoomConfigurationOverrides.GetValueOrDefault(x.Key)?.ImageFilter; - var tasks = x.Select(msg => ProcessEventAsync(room, msg, config)); + var tasks = x.Select(msg => ProcessEventAsync(room, msg, config)).ToList(); await Task.WhenAll(tasks); }); - await Task.WhenAll(tasks); + return Task.WhenAll(tasks); } private async Task ProcessEventAsync(GenericRoom room, StateEventResponse msg, FilterConfiguration.BasicFilterConfiguration roomConfiguration) { if (msg.Type != "m.room.message") return; var content = msg.TypedContent as RoomMessageEventContent; if (content?.MessageType != "m.image") return; - - // await semaphore.WaitAsync(); + metrics.Increment("image_filter_processed_event_count"); await actionQueue.EqueueActionAsync(msg.EventId, async () => { while (true) { @@ -86,7 +79,5 @@ public class ImageFilter( .Build()); }); ActionCount++; - - // semaphore.Release(); } } \ No newline at end of file diff --git a/MatrixContentFilter/Handlers/Filters/PendingInviteLimiter.cs b/MatrixContentFilter/Handlers/Filters/PendingInviteLimiter.cs new file mode 100644
index 0000000..b1b1e8b --- /dev/null +++ b/MatrixContentFilter/Handlers/Filters/PendingInviteLimiter.cs
@@ -0,0 +1,92 @@ +using System.Collections.Concurrent; +using ArcaneLibs.Extensions; +using LibMatrix; +using LibMatrix.EventTypes.Spec.State.RoomInfo; +using LibMatrix.Helpers; +using LibMatrix.Homeservers; +using LibMatrix.Responses; +using LibMatrix.RoomTypes; +using MatrixContentFilter.Abstractions; +using MatrixContentFilter.EventTypes; +using MatrixContentFilter.Services; +using MatrixContentFilter.Services.AsyncActionQueues; + +namespace MatrixContentFilter.Handlers.Filters; + +public class PendingInviteLimiter( + ConfigurationService cfgService, + AuthenticatedHomeserverGeneric hs, + AsyncMessageQueue msgQueue, + InfoCacheService infoCache, + AbstractAsyncActionQueue actionQueue, + MatrixContentFilterMetrics metrics) + : IContentFilter { + public override Task ProcessSyncAsync(SyncResponse syncResponse) { + if (syncResponse.Rooms?.Join is null) return Task.CompletedTask; + var tasks = syncResponse.Rooms.Join.Select(ProcessRoomAsync); + return Task.WhenAll(tasks); + } + + private Task ProcessRoomAsync(KeyValuePair<string, SyncResponse.RoomsDataStructure.JoinedRoomDataStructure> syncRoom) { + var (roomId, roomData) = syncRoom; + if (roomId == cfgService.LogRoom.RoomId || roomId == cfgService.ControlRoom.RoomId) return Task.CompletedTask; + if (roomData.Timeline?.Events is null) return Task.CompletedTask; + var config = cfgService.RoomConfigurationOverrides.GetValueOrDefault(roomId)?.InviteLimiter; + + var room = hs.GetRoom(roomId); + + var tasks = roomData.Timeline.Events.Select(msg => ProcessEventAsync(room, msg, config)).ToList(); + return Task.WhenAll(tasks); + } + + public override Task ProcessEventListAsync(List<StateEventResponse> events) { + var tasks = events.GroupBy(x => x.RoomId).Select(async x => { + var room = hs.GetRoom(x.Key); + var config = cfgService.RoomConfigurationOverrides.GetValueOrDefault(x.Key)?.InviteLimiter; + var tasks = x.Select(msg => ProcessEventAsync(room, msg, config)).ToList(); + await Task.WhenAll(tasks); + }).ToList(); + + return Task.WhenAll(tasks); + } + + // key format: roomid:sender + private ConcurrentDictionary<string, int> HeuristicInviteCount = new(); + private async Task ProcessEventAsync(GenericRoom room, StateEventResponse evt, FilterConfiguration.BasicLimiterConfiguration? roomConfiguration) { + if (evt.Type != "m.room.member") return; + var content = evt.TypedContent as RoomMemberEventContent; + if (content?.Membership != "invite") return; + metrics.Increment("pending_invite_limiter_processed_event_count"); + + var key = $"{evt.RoomId}:{evt.Sender}"; + HeuristicInviteCount.AddOrUpdate(key, 1, (_, count) => count + 1); + if (HeuristicInviteCount[key] > 5) { + await actionQueue.EqueueActionAsync(evt.EventId, async () => { + var displayName = await infoCache.GetDisplayNameAsync(room.RoomId, evt.Sender!); + var roomName = await infoCache.GetRoomNameAsync(room.RoomId); + + msgQueue.EnqueueMessageAsync(cfgService.LogRoom, new MessageBuilder("m.notice") + .WithBody("Pending invite limiter heuristic tripped for ").WithMention(evt.Sender!).WithBody(" in ").WithMention(evt.RoomId, roomName).WithBody($" ({HeuristicInviteCount[key]})!").WithNewline() + .WithBody("Updating heuristics with real counts...") + .Build()); + + var invitedMembersByInviter = (await room.GetMembersListAsync(joinedOnly: false)) + .Where(x => x.ContentAs<RoomMemberEventContent>()!.Membership == "invite") + .GroupBy(x=>x.Sender!); + + foreach (var sender in invitedMembersByInviter) { + HeuristicInviteCount.AddOrUpdate($"{room.RoomId}:{sender.Key}", sender.Count(), (_, count) => count + sender.Count()); + msgQueue.EnqueueMessageAsync(cfgService.LogRoom, new MessageBuilder("m.notice") + .WithBody("Updated heuristic count for ").WithMention(sender.Key).WithBody(" in ").WithMention(room.RoomId, roomName).WithBody($" ({HeuristicInviteCount[key]})!").WithNewline() + .Build()); + } + + // msgQueue.EnqueueMessageAsync(cfgService.LogRoom, new MessageBuilder("m.notice") + // .WithBody("Invite from ").WithMention(evt.Sender, displayName).WithBody(" in ").WithMention(room.RoomId, roomName).WithBody(" was rejected!").WithNewline() + // .WithCollapsibleSection("Message data", msb => msb.WithCodeBlock(content.ToJson(ignoreNull: true), "json")) + // .Build()); + }); + } + ActionCount++; + } +} \ No newline at end of file diff --git a/MatrixContentFilter/Handlers/Filters/VideoFilter.cs b/MatrixContentFilter/Handlers/Filters/VideoFilter.cs new file mode 100644
index 0000000..16a8639 --- /dev/null +++ b/MatrixContentFilter/Handlers/Filters/VideoFilter.cs
@@ -0,0 +1,28 @@ +using ArcaneLibs.Extensions; +using LibMatrix; +using LibMatrix.EventTypes.Spec; +using LibMatrix.Helpers; +using LibMatrix.Homeservers; +using LibMatrix.Responses; +using LibMatrix.RoomTypes; +using MatrixContentFilter.Abstractions; +using MatrixContentFilter.EventTypes; +using MatrixContentFilter.Handlers.Filters.Generic; +using MatrixContentFilter.Services; +using MatrixContentFilter.Services.AsyncActionQueues; + +namespace MatrixContentFilter.Handlers.Filters; + +public class VideoFilter : BasicMessageTypeFilter { + public VideoFilter(ConfigurationService cfgService, + AuthenticatedHomeserverGeneric hs, + AsyncMessageQueue msgQueue, + InfoCacheService infoCache, + AbstractAsyncActionQueue actionQueue, + MatrixContentFilterMetrics metrics + ) : base(cfgService, hs, msgQueue, infoCache, actionQueue, metrics) { + MessageType = "m.video"; + MessageTypeName = "video"; + MessageTypeNamePlural = "videos"; + } +} \ No newline at end of file diff --git a/MatrixContentFilter/Handlers/InviteHandler.cs b/MatrixContentFilter/Handlers/InviteHandler.cs
index 75e5506..2883529 100644 --- a/MatrixContentFilter/Handlers/InviteHandler.cs +++ b/MatrixContentFilter/Handlers/InviteHandler.cs
@@ -1,21 +1,46 @@ using LibMatrix.EventTypes.Spec; using LibMatrix.Helpers; using LibMatrix.Utilities.Bot.Services; +using MatrixContentFilter.Services; +using Microsoft.Extensions.Logging; namespace MatrixContentFilter.Handlers; -public static class InviteHandler { - public static async Task HandleAsync(InviteHandlerHostedService.InviteEventArgs invite) { +public class InviteHandler(ILogger<InviteHandler> logger, ConfigurationService cfg, InfoCacheService infoCache) : InviteHandlerHostedService.IInviteHandler { + public async Task HandleInviteAsync(InviteHandlerHostedService.InviteEventArgs invite) { var room = invite.Homeserver.GetRoom(invite.RoomId); - if (!invite.MemberEvent.Sender!.EndsWith("rory.gay")) { + var controlRoom = invite.Homeserver.GetRoom(cfg.ControlRoom.RoomId); + var logRoom = invite.Homeserver.GetRoom(cfg.LogRoom.RoomId); + + await logRoom.SendMessageEventAsync(new MessageBuilder().WithBody($"Processing invite to {invite.RoomId}...").Build()); + + var roomName = invite.RoomId; + var inviterName = await infoCache.GetDisplayNameAsync(invite.RoomId, invite.MemberEvent.Sender!); + try { + roomName = await infoCache.GetRoomNameAsync(invite.RoomId); + } + catch (Exception) { + logger.LogWarning("Failed to get room name for {RoomId}", invite.RoomId); + } + + var controlRoomMembers = await controlRoom.GetMembersListAsync(joinedOnly: true); + + if (!controlRoomMembers.Any(m => m.StateKey != invite.MemberEvent.Sender)) { await room.LeaveAsync($"{invite.MemberEvent.Sender} is not allowed to invite this bot!"); + await controlRoom.SendMessageEventAsync(new MessageBuilder() + .WithColoredBody("#FF0000", msb => + msb.WithMention(invite.MemberEvent.Sender!, inviterName).WithBody(" attempted to invite the bot to ").WithMention(invite.RoomId, roomName).WithBody(", but is not a member of the control room!") + .WithNewline().WithBody("If you trust this room, the bot should be invited by anyone who can read this message!") + ) + .Build()); return; } try { await room.JoinAsync(reason: $"I was invited by {invite.MemberEvent.Sender}"); await room.SendMessageEventAsync(new RoomMessageEventContent("m.notice", "Hello! I've arrived!")); - } catch (Exception e) { + } + catch (Exception e) { var newroom = await invite.Homeserver.CreateRoom(new() { Name = $"Join error report", Invite = [invite.MemberEvent.Sender] diff --git a/MatrixContentFilter/MatrixContentFilter.csproj b/MatrixContentFilter/MatrixContentFilter.csproj
index ea0ddd1..1a26b13 100644 --- a/MatrixContentFilter/MatrixContentFilter.csproj +++ b/MatrixContentFilter/MatrixContentFilter.csproj
@@ -2,7 +2,7 @@ <PropertyGroup> <OutputType>Exe</OutputType> - <TargetFramework>net8.0</TargetFramework> + <TargetFramework>net9.0</TargetFramework> <LangVersion>preview</LangVersion> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> @@ -22,7 +22,12 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" /> + <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.1" /> + <PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.9.0" /> + <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" /> + <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" /> + <PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.9.0" /> + <PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.9.0" /> </ItemGroup> <ItemGroup> <Content Include="appsettings*.json"> diff --git a/MatrixContentFilter/MatrixContentFilterConfiguration.cs b/MatrixContentFilter/MatrixContentFilterConfiguration.cs
index 57537f0..fb7e8f3 100644 --- a/MatrixContentFilter/MatrixContentFilterConfiguration.cs +++ b/MatrixContentFilter/MatrixContentFilterConfiguration.cs
@@ -10,9 +10,24 @@ public class MatrixContentFilterConfiguration { public string AppMode { get; set; } = "bot"; public string AsyncQueueImplementation { get; set; } = "lifo"; - + public SanityCheckConfiguration SanityCheck { get; set; } = new(); + public OpenTelemetryConfiguration OpenTelemetry { get; set; } = new(); + public class ConcurrencyLimitsConfiguration { public int Redactions { get; set; } = 1; public int LogMessages { get; set; } = 1; } + + public class SanityCheckConfiguration { + public bool Enabled { get; set; } = false; + public int MaxConcurrency { get; set; } = 1; + public TimeSpan Interval { get; set; } = TimeSpan.FromMinutes(5); + } + + public class OpenTelemetryConfiguration { + public bool Enabled { get; set; } = false; + public string Endpoint { get; set; } + public string ServiceName { get; set; } + public string Environment { get; set; } + } } diff --git a/MatrixContentFilter/Program.cs b/MatrixContentFilter/Program.cs
index 7eec930..b849131 100644 --- a/MatrixContentFilter/Program.cs +++ b/MatrixContentFilter/Program.cs
@@ -1,3 +1,4 @@ +using ArcaneLibs.Extensions; using MatrixContentFilter; using MatrixContentFilter.Handlers; using LibMatrix.Services; @@ -8,63 +9,91 @@ using MatrixContentFilter.Services; using MatrixContentFilter.Services.AsyncActionQueues; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.Metrics; using Microsoft.Extensions.Hosting; +using OpenTelemetry.Metrics; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; var builder = Host.CreateDefaultBuilder(args); -builder.ConfigureHostOptions(host => { - host.ServicesStartConcurrently = true; - host.ServicesStopConcurrently = true; - host.ShutdownTimeout = TimeSpan.FromSeconds(5); -}); - if (Environment.GetEnvironmentVariable("MATRIXCONTENTFILTER_APPSETTINGS_PATH") is string path) builder.ConfigureAppConfiguration(x => x.AddJsonFile(path)); var host = builder.ConfigureServices((ctx, services) => { - var config = new MatrixContentFilterConfiguration(ctx.Configuration); - services.AddSingleton<MatrixContentFilterConfiguration>(config); + var config = new MatrixContentFilterConfiguration(ctx.Configuration); + Console.WriteLine("Configuration: {0}", config.ToJson()); + services.AddSingleton<MatrixContentFilterConfiguration>(config); + + services.AddMetrics(m => m.AddDebugConsole()); + services.AddSingleton<MatrixContentFilterMetrics>(); + + if (config.OpenTelemetry.Enabled) { + services.AddOpenTelemetry() + .ConfigureResource(resource => resource.AddService("MatrixContentFilter")) + .WithTracing(tracing => tracing + .AddAspNetCoreInstrumentation() + .AddConsoleExporter()) + .WithMetrics(metrics => metrics + .AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation() + .AddConsoleExporter()); + } + + services.AddRoryLibMatrixServices(new() { + AppName = "MatrixContentFilter" + }); - services.AddRoryLibMatrixServices(new() { - AppName = "MatrixContentFilter" - }); + services.AddMatrixBot().AddCommandHandler().DiscoverAllCommands() + .WithInviteHandler<InviteHandler>() + .WithCommandResultHandler(CommandResultHandler.HandleAsync); - services.AddMatrixBot().AddCommandHandler().DiscoverAllCommands() - .WithInviteHandler(InviteHandler.HandleAsync) - .WithCommandResultHandler(CommandResultHandler.HandleAsync); + services.AddSingleton<InfoCacheService>(); - services.AddSingleton<InfoCacheService>(); + services.AddSingleton<IContentFilter, ImageFilter>(); + services.AddSingleton<IContentFilter, VideoFilter>(); + services.AddSingleton<IContentFilter, PendingInviteLimiter>(); - services.AddSingleton<IContentFilter, ImageFilter>(); + services.AddSingleton<ConfigurationService>(); + services.AddSingleton<IHostedService, ConfigurationService>(s => s.GetRequiredService<ConfigurationService>()); + services.AddSingleton<AsyncMessageQueue>(); + services.AddSingleton<IHostedService, AsyncMessageQueue>(s => s.GetRequiredService<AsyncMessageQueue>()); - services.AddSingleton<ConfigurationService>(); - services.AddSingleton<IHostedService, ConfigurationService>(s => s.GetRequiredService<ConfigurationService>()); - services.AddSingleton<AsyncMessageQueue>(); - services.AddSingleton<IHostedService, AsyncMessageQueue>(s => s.GetRequiredService<AsyncMessageQueue>()); + switch (config.AppMode) { + case "bot": + services.AddHostedService<MatrixContentFilterBot>(); + if (config.SanityCheck.Enabled) + services.AddHostedService<BotModeSanityCheckService>(); + break; + default: + throw new NotSupportedException($"Unknown app mode: {config.AppMode}"); + } - switch (config.AppMode) { - case "bot": - services.AddHostedService<MatrixContentFilterBot>(); - // services.AddHostedService<BotModeSanityCheckService>(); - break; - default: - throw new NotSupportedException($"Unknown app mode: {config.AppMode}"); - } - - switch (config.AsyncQueueImplementation) { - case "lifo": - services.AddSingleton<LiFoAsyncActionQueue>(); - services.AddSingleton<AbstractAsyncActionQueue, LiFoAsyncActionQueue>(s => s.GetRequiredService<LiFoAsyncActionQueue>()); - services.AddSingleton<IHostedService, LiFoAsyncActionQueue>(s => s.GetRequiredService<LiFoAsyncActionQueue>()); - break; - case "fifo": - services.AddSingleton<FiFoAsyncActionQueue>(); - services.AddSingleton<AbstractAsyncActionQueue, FiFoAsyncActionQueue>(s => s.GetRequiredService<FiFoAsyncActionQueue>()); - services.AddSingleton<IHostedService, FiFoAsyncActionQueue>(s => s.GetRequiredService<FiFoAsyncActionQueue>()); - break; - default: - throw new NotSupportedException($"Unknown async queue implementation: {config.AsyncQueueImplementation}"); - } -}).UseConsoleLifetime().Build(); + switch (config.AsyncQueueImplementation) { + case "lifo": + services.AddSingleton<LiFoAsyncActionQueue>(); + services.AddSingleton<AbstractAsyncActionQueue, LiFoAsyncActionQueue>(s => s.GetRequiredService<LiFoAsyncActionQueue>()); + services.AddSingleton<IHostedService, LiFoAsyncActionQueue>(s => s.GetRequiredService<LiFoAsyncActionQueue>()); + break; + case "fifo": + services.AddSingleton<FiFoAsyncActionQueue>(); + services.AddSingleton<AbstractAsyncActionQueue, FiFoAsyncActionQueue>(s => s.GetRequiredService<FiFoAsyncActionQueue>()); + services.AddSingleton<IHostedService, FiFoAsyncActionQueue>(s => s.GetRequiredService<FiFoAsyncActionQueue>()); + break; + default: + throw new NotSupportedException($"Unknown async queue implementation: {config.AsyncQueueImplementation}"); + } + }) + // .ConfigureLogging((ctx, lb) => { + // lb.AddOpenTelemetry(options => { + // options + // .SetResourceBuilder( + // ResourceBuilder.CreateDefault() + // .AddService("MatrixContentFilter")) + // .AddConsoleExporter(); + // }); + // }) + .UseConsoleLifetime().Build(); await host.RunAsync(); \ No newline at end of file diff --git a/MatrixContentFilter/Properties/launchSettings.json b/MatrixContentFilter/Properties/launchSettings.json
index 997e294..b0aa56b 100644 --- a/MatrixContentFilter/Properties/launchSettings.json +++ b/MatrixContentFilter/Properties/launchSettings.json
@@ -21,6 +21,13 @@ "environmentVariables": { "DOTNET_ENVIRONMENT": "Local" } + }, + "MatrixUnitTests": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "DOTNET_ENVIRONMENT": "mut" + } } } } diff --git a/MatrixContentFilter/Services/AsyncActionQueues/AbstractionAsyncActionQueue.cs b/MatrixContentFilter/Services/AsyncActionQueues/AbstractionAsyncActionQueue.cs
index f4c559c..945d264 100644 --- a/MatrixContentFilter/Services/AsyncActionQueues/AbstractionAsyncActionQueue.cs +++ b/MatrixContentFilter/Services/AsyncActionQueues/AbstractionAsyncActionQueue.cs
@@ -1,7 +1,6 @@ using System.Collections.Concurrent; using System.Threading.Channels; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; namespace MatrixContentFilter.Services.AsyncActionQueues; diff --git a/MatrixContentFilter/Services/AsyncActionQueues/FiFoAsyncActionQueue.cs b/MatrixContentFilter/Services/AsyncActionQueues/FiFoAsyncActionQueue.cs
index 3d7c90d..8c67c77 100644 --- a/MatrixContentFilter/Services/AsyncActionQueues/FiFoAsyncActionQueue.cs +++ b/MatrixContentFilter/Services/AsyncActionQueues/FiFoAsyncActionQueue.cs
@@ -1,6 +1,4 @@ -using System.Collections.Concurrent; using System.Threading.Channels; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace MatrixContentFilter.Services.AsyncActionQueues; diff --git a/MatrixContentFilter/Services/BotModeSanityCheckService.cs b/MatrixContentFilter/Services/BotModeSanityCheckService.cs
index 55fe9e8..0a99ca1 100644 --- a/MatrixContentFilter/Services/BotModeSanityCheckService.cs +++ b/MatrixContentFilter/Services/BotModeSanityCheckService.cs
@@ -1,13 +1,8 @@ -using System.Diagnostics; -using ArcaneLibs; using ArcaneLibs.Extensions; -using LibMatrix; using LibMatrix.Filters; using LibMatrix.Helpers; using LibMatrix.Homeservers; -using LibMatrix.Responses; using MatrixContentFilter.Abstractions; -using MatrixContentFilter.Handlers.Filters; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -18,19 +13,22 @@ public class BotModeSanityCheckService( AuthenticatedHomeserverGeneric hs, ConfigurationService filterConfigService, IEnumerable<IContentFilter> filters, - AsyncMessageQueue msgQueue + AsyncMessageQueue msgQueue, + MatrixContentFilterConfiguration cfg ) : BackgroundService { /// <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> protected override async Task ExecuteAsync(CancellationToken cancellationToken) { + var semaphore = new SemaphoreSlim(cfg.SanityCheck.MaxConcurrency); while (!cancellationToken.IsCancellationRequested) { - await Task.Delay(10000, cancellationToken); + await Task.Delay(cfg.SanityCheck.Interval, cancellationToken); var rooms = await hs.GetJoinedRooms(); rooms.RemoveAll(x => x.RoomId == filterConfigService.LogRoom.RoomId); rooms.RemoveAll(x => x.RoomId == filterConfigService.ControlRoom.RoomId); var timelineFilter = new SyncFilter.RoomFilter.StateFilter(notTypes: ["m.room.redaction"], limit: 5000); var timelines = rooms.Select(async x => { + await semaphore.WaitAsync(cancellationToken); var room = hs.GetRoom(x.RoomId); // var sync = await room.GetMessagesAsync(null, 1500, filter: timelineFilter.ToJson(ignoreNull: true, indent: false).UrlEncode()); var iter = room.GetManyMessagesAsync(null, 5000, filter: timelineFilter.ToJson(ignoreNull: true, indent: false).UrlEncode(), chunkSize: 250); @@ -49,6 +47,8 @@ public class BotModeSanityCheckService( await tasks; } + + semaphore.Release(); }).ToList(); await Task.WhenAll(timelines); } diff --git a/MatrixContentFilter/Services/ConfigurationService.cs b/MatrixContentFilter/Services/ConfigurationService.cs
index f83c89a..cd2a93d 100644 --- a/MatrixContentFilter/Services/ConfigurationService.cs +++ b/MatrixContentFilter/Services/ConfigurationService.cs
@@ -1,6 +1,6 @@ using ArcaneLibs.Extensions; using LibMatrix; -using LibMatrix.EventTypes.Spec.State; +using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Helpers; using LibMatrix.Homeservers; using LibMatrix.Responses; @@ -28,7 +28,7 @@ public class ConfigurationService(ILogger<ConfigurationService> logger, Authenti await foreach (var sync in syncHelper.EnumerateSyncAsync(stoppingToken).WithCancellation(stoppingToken)) { if (sync is { AccountData: null, Rooms: null }) continue; - logger.LogInformation("Received configuration update: {syncData}", sync.ToJson(ignoreNull: true)); + logger.LogInformation("Received configuration update..."); await OnSyncReceived(sync); } } @@ -165,6 +165,11 @@ public class ConfigurationService(ILogger<ConfigurationService> logger, Authenti _filterConfiguration.UrlFilter ??= new(); _filterConfiguration.UrlFilter.IgnoredUsers ??= Log("url_filter->ignored_users", (List<string>) []); _filterConfiguration.UrlFilter.Allowed ??= Log("url_filter->allowed", false); + + _filterConfiguration.InviteLimiter ??= new(); + _filterConfiguration.InviteLimiter.IgnoredUsers ??= Log("invite_limiter->ignored_users", (List<string>) []); + _filterConfiguration.InviteLimiter.HeuristicTripPoint ??= Log("invite_limiter->heuristic_trip_point", 5); + _filterConfiguration.InviteLimiter.MaxCount ??= Log("invite_limiter->max_count", 15); if (changes.Count > 0) { await hs.SetAccountDataAsync(FilterConfiguration.EventId, _filterConfiguration); diff --git a/MatrixContentFilter/Services/InfoCacheService.cs b/MatrixContentFilter/Services/InfoCacheService.cs
index 974e873..5f7ab10 100644 --- a/MatrixContentFilter/Services/InfoCacheService.cs +++ b/MatrixContentFilter/Services/InfoCacheService.cs
@@ -1,5 +1,6 @@ using ArcaneLibs.Collections; -using LibMatrix.EventTypes.Spec.State; +using LibMatrix; +using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Homeservers; namespace MatrixContentFilter.Services; @@ -11,8 +12,14 @@ public class InfoCacheService(AuthenticatedHomeserverGeneric hs) { public async Task<string> GetDisplayNameAsync(string roomId, string userId) => await DisplayNameCache.GetOrAdd($"{roomId}\t{userId}", async () => { var room = hs.GetRoom(roomId); - var userState = await room.GetStateAsync<RoomMemberEventContent>(RoomMemberEventContent.EventId, userId); - if (!string.IsNullOrWhiteSpace(userState?.DisplayName)) return userState.DisplayName; + try { + var userState = await room.GetStateOrNullAsync<RoomMemberEventContent>(RoomMemberEventContent.EventId, userId); + if (!string.IsNullOrWhiteSpace(userState?.DisplayName)) return userState.DisplayName; + } + catch (MatrixException e) { + if (e is not { ErrorCode: MatrixException.ErrorCodes.M_NOT_FOUND or MatrixException.ErrorCodes.M_FORBIDDEN }) + throw; + } var user = await hs.GetProfileAsync(userId); if (!string.IsNullOrWhiteSpace(user?.DisplayName)) return user.DisplayName; diff --git a/MatrixContentFilter/Services/MatrixContentFilterBot.cs b/MatrixContentFilter/Services/MatrixContentFilterBot.cs
index 321cdd4..12fa5f6 100644 --- a/MatrixContentFilter/Services/MatrixContentFilterBot.cs +++ b/MatrixContentFilter/Services/MatrixContentFilterBot.cs
@@ -6,7 +6,6 @@ using LibMatrix.Helpers; using LibMatrix.Homeservers; using LibMatrix.Responses; using MatrixContentFilter.Abstractions; -using MatrixContentFilter.Handlers.Filters; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -49,7 +48,7 @@ public class MatrixContentFilterBot( var syncFilter = new SyncFilter() { Room = new() { NotRooms = [filterConfigService.LogRoom.RoomId], - Timeline = new(notTypes: ["m.room.redaction"]) + Timeline = new(notTypes: ["m.room.redaction"], limit: 5000) } }; syncHelper = new SyncHelper(hs, logger) { @@ -114,7 +113,7 @@ public class MatrixContentFilterBot( var room = hs.GetRoom(roomId); if (roomData.Timeline?.Limited == true) { msgQueue.EnqueueMessageAsync(filterConfigService.LogRoom, new MessageBuilder("m.notice") - .WithColoredBody("FF0000", $"Room {roomId} has limited timeline, fetching! The room may be getting spammed?") + .WithColoredBody("#FF0000", $"Room {roomId} has limited timeline, fetching! The room may be getting spammed?") .Build()); roomData.Timeline.Events ??= []; var newEvents = await room.GetMessagesAsync(roomData.Timeline.PrevBatch ?? "", 500, filter: timelineFilter.ToJson(ignoreNull: true, indent: false)); diff --git a/MatrixContentFilter/Services/MatrixContentFilterMetrics.cs b/MatrixContentFilter/Services/MatrixContentFilterMetrics.cs new file mode 100644
index 0000000..ff88cad --- /dev/null +++ b/MatrixContentFilter/Services/MatrixContentFilterMetrics.cs
@@ -0,0 +1,16 @@ +using System.Collections.Frozen; +using System.Diagnostics.Metrics; + +namespace MatrixContentFilter.Services; + +public class MatrixContentFilterMetrics { + private readonly Meter meter = new Meter("MatrixContentFilter"); + private FrozenDictionary<string, Counter<int>> _counters = FrozenDictionary<string, Counter<int>>.Empty; + + public void Increment(string counter, int value = 1) { + if(!_counters.TryGetValue(counter, out var c)) { + c = meter.CreateCounter<int>(counter); + _counters = _counters.Concat([new KeyValuePair<string, Counter<int>>(counter, c)]).ToFrozenDictionary(); + } + } +} \ No newline at end of file diff --git a/MatrixContentFilter/appsettings.Development.json b/MatrixContentFilter/appsettings.Development.json
index 29f9c88..b01a410 100644 --- a/MatrixContentFilter/appsettings.Development.json +++ b/MatrixContentFilter/appsettings.Development.json
@@ -15,10 +15,26 @@ "Prefix": "?" }, "MatrixContentFilter": { + // App mode: bot or appservice + "AppMode": "bot", + // whether to use LiFo or FiFo for task queuing + "AsyncQueueImplementation": "lifo", // List of people who should be invited to the control room "Admins": [ "@emma:conduit.rory.gay", "@emma:rory.gay" - ] + ], + // The maximum number of redactions that can be performed concurrently + "ConcurrencyLimits": { + "Redactions": 2, + "LogMessages": 2 + }, + // Whether bot mode should be ammended with a cronjob to check for missed events + // Keep in mind this may add a lot of load to the server + "SanityCheck": { + "Enabled": false, + "MaxConcurrency": 1, + "Interval": "00:05:00" + } } } diff --git a/result b/result deleted file mode 120000
index c324b17..0000000 --- a/result +++ /dev/null
@@ -1 +0,0 @@ -/nix/store/8l17zckl65pjn01h3fawbr9nw6306jvg-MatrixContentFilter-v1-1 \ No newline at end of file