From a5bbc592291429011b9050d19f49dc5267acc556 Mon Sep 17 00:00:00 2001 From: wtfblub Date: Mon, 14 May 2018 13:56:18 +0200 Subject: [PATCH 1/2] Exclude known bots from earning coins while in chat --- src/DevChatter.Bot.Core/ChatUserCollection.cs | 30 ++- .../Data/Model/ChatUser.cs | 1 + .../Events/CurrencyGenerator.cs | 5 +- .../IChatUserCollection.cs | 4 +- .../Systems/Chat/IKnownBotService.cs | 9 + ...513054353_Add-IsKnownBotColumn.Designer.cs | 181 ++++++++++++++++++ .../20180513054353_Add-IsKnownBotColumn.cs | 22 +++ .../Migrations/AppDataContextModelSnapshot.cs | 2 + .../DevChatterBotTwitchModule.cs | 2 + .../TwitchKnownBotService.cs | 37 ++++ .../CurrencyUpdateTests/IsTimeToRunShould.cs | 4 +- .../AddCurrencyToShould.cs | 8 +- 12 files changed, 291 insertions(+), 14 deletions(-) create mode 100644 src/DevChatter.Bot.Core/Systems/Chat/IKnownBotService.cs create mode 100644 src/DevChatter.Bot.Infra.Ef/Migrations/20180513054353_Add-IsKnownBotColumn.Designer.cs create mode 100644 src/DevChatter.Bot.Infra.Ef/Migrations/20180513054353_Add-IsKnownBotColumn.cs create mode 100644 src/DevChatter.Bot.Infra.Twitch/TwitchKnownBotService.cs diff --git a/src/DevChatter.Bot.Core/ChatUserCollection.cs b/src/DevChatter.Bot.Core/ChatUserCollection.cs index a1fffedf..7b70f4ee 100644 --- a/src/DevChatter.Bot.Core/ChatUserCollection.cs +++ b/src/DevChatter.Bot.Core/ChatUserCollection.cs @@ -5,37 +5,55 @@ using DevChatter.Bot.Core.Data.Model; using DevChatter.Bot.Core.Data.Specifications; using DevChatter.Bot.Core.Extensions; +using DevChatter.Bot.Core.Systems.Chat; namespace DevChatter.Bot.Core { public class ChatUserCollection : IChatUserCollection { private readonly IRepository _repository; + private readonly IKnownBotService _knownBotService; private readonly object _userCreationLock = new object(); private readonly object _activeChatUsersLock = new object(); private readonly List _activeChatUsers = new List(); - public ChatUserCollection(IRepository repository) + public ChatUserCollection(IRepository repository, IKnownBotService knownBotService) { _repository = repository; + _knownBotService = knownBotService; } - public bool NeedToWatchUser(string displayName) + public bool NeedToWatchUser(string displayName, ChatUser chatUser) { + ChatUser chatUserFromDb = GetOrCreateChatUser(displayName, chatUser); + + if (chatUserFromDb.IsKnownBot == null) + { + lock (_activeChatUsersLock) + { + bool isKnownBot = _knownBotService.IsKnownBot(chatUserFromDb.DisplayName).GetAwaiter().GetResult(); + chatUserFromDb.IsKnownBot = isKnownBot; + _repository.Update(chatUserFromDb); + } + } + + if (chatUserFromDb.IsKnownBot.Value) + return false; + // Don't lock in here // ReSharper disable once InconsistentlySynchronizedField return _activeChatUsers.All(activeDisplayName => activeDisplayName != displayName); } - public void WatchUser(string displayName) + public void WatchUser(string displayName, ChatUser chatUser) { - if (NeedToWatchUser(displayName)) + if (NeedToWatchUser(displayName, chatUser)) { lock (_activeChatUsersLock) { - if (NeedToWatchUser(displayName)) + if (NeedToWatchUser(displayName, chatUser)) { _activeChatUsers.Add(displayName); } @@ -74,7 +92,7 @@ public void StopWatching(string displayName) public bool UserHasAtLeast(string username, int tokensToRemove) { ChatUser chatUser = GetOrCreateChatUser(username); - WatchUser(chatUser.DisplayName); + WatchUser(chatUser.DisplayName, chatUser); return chatUser.Tokens >= tokensToRemove; } diff --git a/src/DevChatter.Bot.Core/Data/Model/ChatUser.cs b/src/DevChatter.Bot.Core/Data/Model/ChatUser.cs index b054400d..a448c2a3 100644 --- a/src/DevChatter.Bot.Core/Data/Model/ChatUser.cs +++ b/src/DevChatter.Bot.Core/Data/Model/ChatUser.cs @@ -8,6 +8,7 @@ public class ChatUser : DataEntity public string DisplayName { get; set; } public UserRole? Role { get; set; } public int Tokens { get; set; } + public bool? IsKnownBot { get; set; } public bool CanRunCommand(IBotCommand botCommand) { diff --git a/src/DevChatter.Bot.Core/Events/CurrencyGenerator.cs b/src/DevChatter.Bot.Core/Events/CurrencyGenerator.cs index af1aaf11..688fb319 100644 --- a/src/DevChatter.Bot.Core/Events/CurrencyGenerator.cs +++ b/src/DevChatter.Bot.Core/Events/CurrencyGenerator.cs @@ -37,14 +37,13 @@ private void ChatClientOnOnUserNoticed(object sender, UserStatusEventArgs eventA private void WatchUserIfNeeded(string displayName, ChatUser chatUser) { - if (_chatUserCollection.NeedToWatchUser(displayName)) + if (_chatUserCollection.NeedToWatchUser(displayName, chatUser)) { ChatUser userFromDb = _chatUserCollection.GetOrCreateChatUser(displayName, chatUser); - _chatUserCollection.WatchUser(userFromDb.DisplayName); + _chatUserCollection.WatchUser(userFromDb.DisplayName, userFromDb); } } - private void ChatClientOnUserLeft(object sender, UserStatusEventArgs eventArgs) { _chatUserCollection.GetOrCreateChatUser(eventArgs.DisplayName, eventArgs.ToChatUser()); diff --git a/src/DevChatter.Bot.Core/IChatUserCollection.cs b/src/DevChatter.Bot.Core/IChatUserCollection.cs index 2039729f..714b0c69 100644 --- a/src/DevChatter.Bot.Core/IChatUserCollection.cs +++ b/src/DevChatter.Bot.Core/IChatUserCollection.cs @@ -7,12 +7,12 @@ namespace DevChatter.Bot.Core public interface IChatUserCollection { ChatUser GetOrCreateChatUser(string displayName, ChatUser chatUser = null); - bool NeedToWatchUser(string displayName); + bool NeedToWatchUser(string displayName, ChatUser chatUser); void StopWatching(string displayName); bool TryGiveCoins(string coinGiver, string coinReceiver, int coinsToGive); void UpdateEachChatter(Action updateToApply); void UpdateSpecificChatters(Action updateToApply, ISpecification filter); bool UserHasAtLeast(string username, int tokensToRemove); - void WatchUser(string displayName); + void WatchUser(string displayName, ChatUser chatUser); } } diff --git a/src/DevChatter.Bot.Core/Systems/Chat/IKnownBotService.cs b/src/DevChatter.Bot.Core/Systems/Chat/IKnownBotService.cs new file mode 100644 index 00000000..c1a1fbc7 --- /dev/null +++ b/src/DevChatter.Bot.Core/Systems/Chat/IKnownBotService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace DevChatter.Bot.Core.Systems.Chat +{ + public interface IKnownBotService + { + Task IsKnownBot(string username); + } +} diff --git a/src/DevChatter.Bot.Infra.Ef/Migrations/20180513054353_Add-IsKnownBotColumn.Designer.cs b/src/DevChatter.Bot.Infra.Ef/Migrations/20180513054353_Add-IsKnownBotColumn.Designer.cs new file mode 100644 index 00000000..adbbfb24 --- /dev/null +++ b/src/DevChatter.Bot.Infra.Ef/Migrations/20180513054353_Add-IsKnownBotColumn.Designer.cs @@ -0,0 +1,181 @@ +// +using DevChatter.Bot.Core.Data.Model; +using DevChatter.Bot.Infra.Ef; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using System; + +namespace DevChatter.Bot.Infra.Ef.Migrations +{ + [DbContext(typeof(AppDataContext))] + [Migration("20180513054353_Add-IsKnownBotColumn")] + partial class AddIsKnownBotColumn + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.2-rtm-10011") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("DevChatter.Bot.Core.Commands.SimpleCommand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandText"); + + b.Property("HelpText"); + + b.Property("RoleRequired"); + + b.Property("StaticResponse"); + + b.HasKey("Id"); + + b.ToTable("SimpleCommands"); + }); + + modelBuilder.Entity("DevChatter.Bot.Core.Data.Model.ChatUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DisplayName"); + + b.Property("IsKnownBot"); + + b.Property("Role"); + + b.Property("Tokens"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("ChatUsers"); + }); + + modelBuilder.Entity("DevChatter.Bot.Core.Data.Model.CommandUsageEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChatClientUsed"); + + b.Property("CommandWord"); + + b.Property("DateTimeUsed"); + + b.Property("FullTypeName"); + + b.Property("UserDisplayName"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("CommandUsages"); + }); + + modelBuilder.Entity("DevChatter.Bot.Core.Data.Model.CommandWordEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandWord"); + + b.Property("FullTypeName"); + + b.Property("IsPrimary"); + + b.HasKey("Id"); + + b.HasIndex("CommandWord"); + + b.ToTable("CommandWords"); + }); + + modelBuilder.Entity("DevChatter.Bot.Core.Data.Model.HangmanWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Word"); + + b.HasKey("Id"); + + b.ToTable("HangmanWords"); + }); + + modelBuilder.Entity("DevChatter.Bot.Core.Data.Model.IntervalMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DelayInMinutes"); + + b.Property("MessageText"); + + b.HasKey("Id"); + + b.ToTable("IntervalMessages"); + }); + + modelBuilder.Entity("DevChatter.Bot.Core.Data.Model.QuoteEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedBy"); + + b.Property("Author"); + + b.Property("DateAdded"); + + b.Property("QuoteId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("QuoteId"); + + b.ToTable("QuoteEntities"); + }); + + modelBuilder.Entity("DevChatter.Bot.Core.Data.Model.ScheduleEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ExampleDateTime"); + + b.HasKey("Id"); + + b.ToTable("ScheduleEntities"); + }); + + modelBuilder.Entity("DevChatter.Bot.Core.Data.Model.StreamerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelName"); + + b.Property("DateAdded"); + + b.Property("TimesShoutedOut"); + + b.HasKey("Id"); + + b.ToTable("Streamers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/DevChatter.Bot.Infra.Ef/Migrations/20180513054353_Add-IsKnownBotColumn.cs b/src/DevChatter.Bot.Infra.Ef/Migrations/20180513054353_Add-IsKnownBotColumn.cs new file mode 100644 index 00000000..9f198843 --- /dev/null +++ b/src/DevChatter.Bot.Infra.Ef/Migrations/20180513054353_Add-IsKnownBotColumn.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace DevChatter.Bot.Infra.Ef.Migrations +{ + public partial class AddIsKnownBotColumn : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsKnownBot", + table: "ChatUsers", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsKnownBot", + table: "ChatUsers"); + } + } +} diff --git a/src/DevChatter.Bot.Infra.Ef/Migrations/AppDataContextModelSnapshot.cs b/src/DevChatter.Bot.Infra.Ef/Migrations/AppDataContextModelSnapshot.cs index 0135e628..33e3fcd0 100644 --- a/src/DevChatter.Bot.Infra.Ef/Migrations/AppDataContextModelSnapshot.cs +++ b/src/DevChatter.Bot.Infra.Ef/Migrations/AppDataContextModelSnapshot.cs @@ -46,6 +46,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("DisplayName"); + b.Property("IsKnownBot"); + b.Property("Role"); b.Property("Tokens"); diff --git a/src/DevChatter.Bot.Infra.Twitch/DevChatterBotTwitchModule.cs b/src/DevChatter.Bot.Infra.Twitch/DevChatterBotTwitchModule.cs index 8442af24..0c6b7fca 100644 --- a/src/DevChatter.Bot.Infra.Twitch/DevChatterBotTwitchModule.cs +++ b/src/DevChatter.Bot.Infra.Twitch/DevChatterBotTwitchModule.cs @@ -23,6 +23,8 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType().AsImplementedInterfaces().SingleInstance(); builder.RegisterType().AsImplementedInterfaces().SingleInstance(); + + builder.RegisterType().AsImplementedInterfaces().SingleInstance(); } } } diff --git a/src/DevChatter.Bot.Infra.Twitch/TwitchKnownBotService.cs b/src/DevChatter.Bot.Infra.Twitch/TwitchKnownBotService.cs new file mode 100644 index 00000000..92ba9e36 --- /dev/null +++ b/src/DevChatter.Bot.Infra.Twitch/TwitchKnownBotService.cs @@ -0,0 +1,37 @@ +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using DevChatter.Bot.Core.Systems.Chat; + +namespace DevChatter.Bot.Infra.Twitch +{ + public class TwitchKnownBotService : IKnownBotService + { + private const string API_ENDPOINT = "https://api.twitchbots.info/v1/"; + + private readonly HttpClient _client; + + public TwitchKnownBotService() + { + _client = new HttpClient(); + } + + public async Task IsKnownBot(string username) + { + HttpResponseMessage response = await _client.GetAsync($"{API_ENDPOINT}bot/{username}").ConfigureAwait(false); + + switch (response.StatusCode) + { + case HttpStatusCode.NotFound: + return false; + + case HttpStatusCode.OK: + return true; + + default: + throw new HttpRequestException( + $"Unable to fetch known bot. Service returned {(int) response.StatusCode} {response.StatusCode}"); + } + } + } +} diff --git a/src/UnitTests/Core/Automation/CurrencyUpdateTests/IsTimeToRunShould.cs b/src/UnitTests/Core/Automation/CurrencyUpdateTests/IsTimeToRunShould.cs index 5e582d93..78dd10a1 100644 --- a/src/UnitTests/Core/Automation/CurrencyUpdateTests/IsTimeToRunShould.cs +++ b/src/UnitTests/Core/Automation/CurrencyUpdateTests/IsTimeToRunShould.cs @@ -42,7 +42,9 @@ public void ReturnFalse_AfterInvokingAction() const int intervalInMinutes = 1; var repository = new Mock(); repository.Setup(x => x.List(It.IsAny>())).Returns(new List()); - var currencyGenerator = new CurrencyGenerator(new List(), new ChatUserCollection(repository.Object)); + var knownBotService = new Mock(); + var currencyGenerator = new CurrencyGenerator(new List(), + new ChatUserCollection(repository.Object, knownBotService.Object)); var fakeClock = new FakeClock(); var currencyUpdate = new CurrencyUpdate(intervalInMinutes, currencyGenerator, fakeClock); diff --git a/src/UnitTests/Core/Events/CurrencyGeneratorTests/AddCurrencyToShould.cs b/src/UnitTests/Core/Events/CurrencyGeneratorTests/AddCurrencyToShould.cs index 402830dc..78c63de9 100644 --- a/src/UnitTests/Core/Events/CurrencyGeneratorTests/AddCurrencyToShould.cs +++ b/src/UnitTests/Core/Events/CurrencyGeneratorTests/AddCurrencyToShould.cs @@ -18,7 +18,9 @@ public class AddCurrencyToShould public void AllowGoingToMaxInt_GivenZeroStartAndMaxIntAdded() { var mockRepo = new Mock(); - var currencyGenerator = new CurrencyGenerator(new List(), new ChatUserCollection(mockRepo.Object)); + var mockKnownBot = new Mock(); + var currencyGenerator = new CurrencyGenerator(new List(), + new ChatUserCollection(mockRepo.Object, mockKnownBot.Object)); var chatUser = new ChatUser {Tokens = 0}; mockRepo.Setup(x => x.List(It.IsAny())).Returns(new List { chatUser }); @@ -32,7 +34,9 @@ public void AllowGoingToMaxInt_GivenZeroStartAndMaxIntAdded() public void CapAtMaxInt_GivenOverflowPossibility() { var mockRepo = new Mock(); - var currencyGenerator = new CurrencyGenerator(new List(), new ChatUserCollection(mockRepo.Object)); + var mockKnownBot = new Mock(); + var currencyGenerator = new CurrencyGenerator(new List(), + new ChatUserCollection(mockRepo.Object, mockKnownBot.Object)); var chatUser = new ChatUser {Tokens = 100}; mockRepo.Setup(x => x.List(It.IsAny())).Returns(new List { chatUser }); From adf98b91ec07c2a7843fd7d483a72fef7819c8e4 Mon Sep 17 00:00:00 2001 From: wtfblub Date: Tue, 15 May 2018 06:54:59 +0200 Subject: [PATCH 2/2] Remove unnecessary ChatUser parameter --- src/DevChatter.Bot.Core/ChatUserCollection.cs | 12 ++++++------ src/DevChatter.Bot.Core/Events/CurrencyGenerator.cs | 8 +++++--- src/DevChatter.Bot.Core/IChatUserCollection.cs | 4 ++-- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/DevChatter.Bot.Core/ChatUserCollection.cs b/src/DevChatter.Bot.Core/ChatUserCollection.cs index 7b70f4ee..0e69b5ae 100644 --- a/src/DevChatter.Bot.Core/ChatUserCollection.cs +++ b/src/DevChatter.Bot.Core/ChatUserCollection.cs @@ -24,9 +24,9 @@ public ChatUserCollection(IRepository repository, IKnownBotService knownBotServi _knownBotService = knownBotService; } - public bool NeedToWatchUser(string displayName, ChatUser chatUser) + public bool NeedToWatchUser(string displayName) { - ChatUser chatUserFromDb = GetOrCreateChatUser(displayName, chatUser); + ChatUser chatUserFromDb = GetOrCreateChatUser(displayName); if (chatUserFromDb.IsKnownBot == null) { @@ -47,13 +47,13 @@ public bool NeedToWatchUser(string displayName, ChatUser chatUser) } - public void WatchUser(string displayName, ChatUser chatUser) + public void WatchUser(string displayName) { - if (NeedToWatchUser(displayName, chatUser)) + if (NeedToWatchUser(displayName)) { lock (_activeChatUsersLock) { - if (NeedToWatchUser(displayName, chatUser)) + if (NeedToWatchUser(displayName)) { _activeChatUsers.Add(displayName); } @@ -92,7 +92,7 @@ public void StopWatching(string displayName) public bool UserHasAtLeast(string username, int tokensToRemove) { ChatUser chatUser = GetOrCreateChatUser(username); - WatchUser(chatUser.DisplayName, chatUser); + WatchUser(chatUser.DisplayName); return chatUser.Tokens >= tokensToRemove; } diff --git a/src/DevChatter.Bot.Core/Events/CurrencyGenerator.cs b/src/DevChatter.Bot.Core/Events/CurrencyGenerator.cs index 688fb319..09514c7c 100644 --- a/src/DevChatter.Bot.Core/Events/CurrencyGenerator.cs +++ b/src/DevChatter.Bot.Core/Events/CurrencyGenerator.cs @@ -37,10 +37,12 @@ private void ChatClientOnOnUserNoticed(object sender, UserStatusEventArgs eventA private void WatchUserIfNeeded(string displayName, ChatUser chatUser) { - if (_chatUserCollection.NeedToWatchUser(displayName, chatUser)) + // Calling this here to make sure the user is in the database before calling NeedToWatchUser + ChatUser userFromDb = _chatUserCollection.GetOrCreateChatUser(displayName, chatUser); + + if (_chatUserCollection.NeedToWatchUser(displayName)) { - ChatUser userFromDb = _chatUserCollection.GetOrCreateChatUser(displayName, chatUser); - _chatUserCollection.WatchUser(userFromDb.DisplayName, userFromDb); + _chatUserCollection.WatchUser(userFromDb.DisplayName); } } diff --git a/src/DevChatter.Bot.Core/IChatUserCollection.cs b/src/DevChatter.Bot.Core/IChatUserCollection.cs index 714b0c69..2039729f 100644 --- a/src/DevChatter.Bot.Core/IChatUserCollection.cs +++ b/src/DevChatter.Bot.Core/IChatUserCollection.cs @@ -7,12 +7,12 @@ namespace DevChatter.Bot.Core public interface IChatUserCollection { ChatUser GetOrCreateChatUser(string displayName, ChatUser chatUser = null); - bool NeedToWatchUser(string displayName, ChatUser chatUser); + bool NeedToWatchUser(string displayName); void StopWatching(string displayName); bool TryGiveCoins(string coinGiver, string coinReceiver, int coinsToGive); void UpdateEachChatter(Action updateToApply); void UpdateSpecificChatters(Action updateToApply, ISpecification filter); bool UserHasAtLeast(string username, int tokensToRemove); - void WatchUser(string displayName, ChatUser chatUser); + void WatchUser(string displayName); } }