diff --git a/src/Nullinside.Api.TwitchBot.Tests/ChatRules/AChatRuleUnitTestBase.cs b/src/Nullinside.Api.TwitchBot.Tests/ChatRules/AChatRuleUnitTestBase.cs
index 8a76b16..50f73a9 100644
--- a/src/Nullinside.Api.TwitchBot.Tests/ChatRules/AChatRuleUnitTestBase.cs
+++ b/src/Nullinside.Api.TwitchBot.Tests/ChatRules/AChatRuleUnitTestBase.cs
@@ -1,8 +1,10 @@
using Moq;
using Nullinside.Api.Common.Twitch;
-using Nullinside.Api.Model.Ddl;
using Nullinside.Api.TwitchBot.ChatRules;
+using Nullinside.Api.TwitchBot.Model;
+
+using TwitchUserConfig = Nullinside.Api.Model.Ddl.TwitchUserConfig;
namespace Nullinside.Api.TwitchBot.Tests.ChatRules;
diff --git a/src/Nullinside.Api.TwitchBot.Tests/ChatRules/BestCheapViewersTests.cs b/src/Nullinside.Api.TwitchBot.Tests/ChatRules/BestCheapViewersTests.cs
index 9f57cfc..88007ed 100644
--- a/src/Nullinside.Api.TwitchBot.Tests/ChatRules/BestCheapViewersTests.cs
+++ b/src/Nullinside.Api.TwitchBot.Tests/ChatRules/BestCheapViewersTests.cs
@@ -2,6 +2,7 @@
using Nullinside.Api.Common.Twitch;
using Nullinside.Api.TwitchBot.ChatRules;
+using Nullinside.Api.TwitchBot.Model;
namespace Nullinside.Api.TwitchBot.Tests.ChatRules;
diff --git a/src/Nullinside.Api.TwitchBot.Tests/ChatRules/DiscordTests.cs b/src/Nullinside.Api.TwitchBot.Tests/ChatRules/DiscordTests.cs
index d309d7f..6c99848 100644
--- a/src/Nullinside.Api.TwitchBot.Tests/ChatRules/DiscordTests.cs
+++ b/src/Nullinside.Api.TwitchBot.Tests/ChatRules/DiscordTests.cs
@@ -2,6 +2,7 @@
using Nullinside.Api.Common.Twitch;
using Nullinside.Api.TwitchBot.ChatRules;
+using Nullinside.Api.TwitchBot.Model;
namespace Nullinside.Api.TwitchBot.Tests.ChatRules;
diff --git a/src/Nullinside.Api.TwitchBot.Tests/ChatRules/DoghypeTests.cs b/src/Nullinside.Api.TwitchBot.Tests/ChatRules/DoghypeTests.cs
index d628e34..e1e71ce 100644
--- a/src/Nullinside.Api.TwitchBot.Tests/ChatRules/DoghypeTests.cs
+++ b/src/Nullinside.Api.TwitchBot.Tests/ChatRules/DoghypeTests.cs
@@ -2,6 +2,7 @@
using Nullinside.Api.Common.Twitch;
using Nullinside.Api.TwitchBot.ChatRules;
+using Nullinside.Api.TwitchBot.Model;
namespace Nullinside.Api.TwitchBot.Tests.ChatRules;
diff --git a/src/Nullinside.Api.TwitchBot.Tests/ChatRules/NezhnaTests.cs b/src/Nullinside.Api.TwitchBot.Tests/ChatRules/NezhnaTests.cs
index e2c3f2d..a6606a7 100644
--- a/src/Nullinside.Api.TwitchBot.Tests/ChatRules/NezhnaTests.cs
+++ b/src/Nullinside.Api.TwitchBot.Tests/ChatRules/NezhnaTests.cs
@@ -2,6 +2,7 @@
using Nullinside.Api.Common.Twitch;
using Nullinside.Api.TwitchBot.ChatRules;
+using Nullinside.Api.TwitchBot.Model;
namespace Nullinside.Api.TwitchBot.Tests.ChatRules;
diff --git a/src/Nullinside.Api.TwitchBot.Tests/ChatRules/StreamViewersTests.cs b/src/Nullinside.Api.TwitchBot.Tests/ChatRules/StreamViewersTests.cs
index 7787e77..fd6cea7 100644
--- a/src/Nullinside.Api.TwitchBot.Tests/ChatRules/StreamViewersTests.cs
+++ b/src/Nullinside.Api.TwitchBot.Tests/ChatRules/StreamViewersTests.cs
@@ -2,6 +2,7 @@
using Nullinside.Api.Common.Twitch;
using Nullinside.Api.TwitchBot.ChatRules;
+using Nullinside.Api.TwitchBot.Model;
namespace Nullinside.Api.TwitchBot.Tests.ChatRules;
diff --git a/src/Nullinside.Api.TwitchBot/Bots/BanKnownBots.cs b/src/Nullinside.Api.TwitchBot/Bots/BanKnownBots.cs
index c1dfb4b..65035e8 100644
--- a/src/Nullinside.Api.TwitchBot/Bots/BanKnownBots.cs
+++ b/src/Nullinside.Api.TwitchBot/Bots/BanKnownBots.cs
@@ -1,198 +1,198 @@
-using System.Collections.Immutable;
-using System.Diagnostics;
-using System.Text;
-
-using Newtonsoft.Json;
-
-using Nullinside.Api.Common.Twitch;
-using Nullinside.Api.Model;
-using Nullinside.Api.Model.Ddl;
-using Nullinside.Api.TwitchBot.Model;
-
-using TwitchLib.Api.Helix.Models.Chat.GetChatters;
-
-using TwitchUserConfig = Nullinside.Api.Model.Ddl.TwitchUserConfig;
-
-namespace Nullinside.Api.TwitchBot.Bots;
-
-///
-/// Searches for and bans known bots when they enter the chat.
-///
-public class BanKnownBots : ABotRule {
- ///
- /// Handles refreshing the collection.
- ///
- private static Task _ = Task.Run(async () => {
- while (true) {
- Task?> twitchInsights = GetTwitchInsightsBots();
- Task?> commanderRoot = GetCommanderRootBots();
- await Task.WhenAll(twitchInsights, commanderRoot);
- if (null != twitchInsights.Result) {
- KnownBotListUsername = twitchInsights.Result;
- }
-
- if (null != commanderRoot.Result) {
- KnownBotListUserId = commanderRoot.Result;
- }
-
- // The list is enormous, force garbage collection.
- GC.Collect();
- GC.WaitForPendingFinalizers();
- await Task.Delay(TimeSpan.FromMinutes(10));
- }
- });
-
- ///
- /// The cache of known bots.
- ///
- public static ImmutableHashSet? KnownBotListUsername { get; set; }
-
- ///
- /// The cache of known bots.
- ///
- public static ImmutableHashSet? KnownBotListUserId { get; set; }
-
- ///
- /// Determine if the rule is enabled in the configuration.
- ///
- /// The user's configuration.
- /// True if it should run, false otherwise.
- public override bool ShouldRun(TwitchUserConfig config) {
- return config is { Enabled: true, BanKnownBots: true };
- }
-
- ///
- /// Searches for and bans known bots when they enter the chat.
- ///
- /// The user.
- /// The user's configuration.
- /// The twitch api authenticated as the bot user.
- /// The database.
- /// The cancellation token.
- public override async Task Handle(User user, TwitchUserConfig config, ITwitchApiProxy botProxy,
- INullinsideContext db, CancellationToken stoppingToken = new()) {
- if (null == user.TwitchId) {
- return;
- }
-
- // TODO: SKIP MODS AND VIPS
-
- // Get the list of people in the chat.
- List? chatters =
- (await botProxy.GetChannelUsers(user.TwitchId, Constants.BotId, stoppingToken))?.ToList();
- if (null == chatters || chatters.Count == 0) {
- return;
- }
-
- // Perform the comparison in the lock to prevent multithreading issues.
- // The collection is extremely large so we do not want to make a copy of it.
- var botsInChatInsights = new List();
- var botsInChatCommanderRoot = new List();
- ImmutableHashSet? knownBotUsernames = KnownBotListUsername;
- ImmutableHashSet? knownBotUserIds = KnownBotListUserId;
-
- if (null != knownBotUsernames) {
- botsInChatInsights.AddRange(chatters.Where(k =>
- knownBotUsernames.Contains(k.UserLogin.ToLowerInvariant())));
- }
-
- if (null != knownBotUserIds) {
- botsInChatCommanderRoot.AddRange(chatters.Where(k =>
- knownBotUserIds.Contains(k.UserId.ToLowerInvariant())));
- }
-
- // Remove the whitelisted bots
- botsInChatInsights = botsInChatInsights
- .Where(b => !Constants.WhitelistedBots.Contains(b.UserLogin.ToLowerInvariant())).ToList();
- botsInChatCommanderRoot = botsInChatCommanderRoot
- .Where(b => !Constants.WhitelistedBots.Contains(b.UserLogin.ToLowerInvariant())).ToList();
-
- // Ban them.
- if (botsInChatInsights.Count != 0) {
- await BanOnce(botProxy, db, user.TwitchId, botsInChatInsights.Select(b => (Id: b.UserId, Username: b.UserLogin)),
- "[Bot] Username on Known Bot List", stoppingToken);
- }
-
- if (botsInChatCommanderRoot.Count != 0) {
- await BanOnce(botProxy, db, user.TwitchId,
- botsInChatCommanderRoot.Select(b => (Id: b.UserId, Username: b.UserLogin)),
- "[Bot] Username on Known Bot List", stoppingToken);
- }
- }
-
- ///
- /// Gets the list of all known bots.
- ///
- /// The list of bots if successful, null otherwise.
- private static async Task?> GetTwitchInsightsBots() {
- var stopwatch = new Stopwatch();
- stopwatch.Start();
- // Reach out to the api and find out what bots are online.
- using var http = new HttpClient();
- HttpResponseMessage response = await http.GetAsync("https://api.twitchinsights.net/v1/bots/all");
- if (!response.IsSuccessStatusCode) {
- return null;
- }
-
- byte[]? content = await response.Content.ReadAsByteArrayAsync();
- string? jsonString = Encoding.UTF8.GetString(content);
- var liveBotsResponse = JsonConvert.DeserializeObject(jsonString);
- if (null == liveBotsResponse) {
- return null;
- }
-
- ImmutableHashSet allBots = liveBotsResponse.bots
- .Where(s => !string.IsNullOrWhiteSpace(s[0].ToString()))
-#pragma warning disable 8602
- .Select(s => s[0].ToString().ToLowerInvariant())
-#pragma warning restore 8602
- .ToImmutableHashSet();
-
- foreach (List