Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exclude known bots from earning coins while in chat #179

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/DevChatter.Bot.Core/ChatUserCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,42 @@
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 object _currencyLock = new object();
private readonly List<string> _activeChatUsers = new List<string>();

public ChatUserCollection(IRepository repository)
public ChatUserCollection(IRepository repository, IKnownBotService knownBotService)
{
_repository = repository;
_knownBotService = knownBotService;
}

public bool NeedToWatchUser(string displayName)
{
ChatUser chatUserFromDb = GetOrCreateChatUser(displayName);

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(name => name != displayName);
Expand Down
1 change: 1 addition & 0 deletions src/DevChatter.Bot.Core/Data/Model/ChatUser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
5 changes: 3 additions & 2 deletions src/DevChatter.Bot.Core/Events/CurrencyGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,15 @@ private void ChatClientOnOnUserNoticed(object sender, UserStatusEventArgs eventA

private void WatchUserIfNeeded(string displayName, ChatUser 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);
}
}


private void ChatClientOnUserLeft(object sender, UserStatusEventArgs eventArgs)
{
_chatUserCollection.GetOrCreateChatUser(eventArgs.DisplayName, eventArgs.ToChatUser());
Expand Down
9 changes: 9 additions & 0 deletions src/DevChatter.Bot.Core/Systems/Chat/IKnownBotService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Threading.Tasks;

namespace DevChatter.Bot.Core.Systems.Chat
{
public interface IKnownBotService
{
Task<bool> IsKnownBot(string username);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -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<bool>(
name: "IsKnownBot",
table: "ChatUsers",
nullable: true);
}

protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsKnownBot",
table: "ChatUsers");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ protected override void BuildModel(ModelBuilder modelBuilder)

b.Property<string>("DisplayName");

b.Property<bool?>("IsKnownBot");

b.Property<int?>("Role");

b.Property<int>("Tokens");
Expand Down
2 changes: 2 additions & 0 deletions src/DevChatter.Bot.Infra.Twitch/DevChatterBotTwitchModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ protected override void Load(ContainerBuilder builder)
builder.RegisterType<TwitchChatClient>().AsImplementedInterfaces().SingleInstance();

builder.RegisterType<TwitchStreamingInfoService>().AsImplementedInterfaces().SingleInstance();

builder.RegisterType<TwitchKnownBotService>().AsImplementedInterfaces().SingleInstance();
}
}
}
37 changes: 37 additions & 0 deletions src/DevChatter.Bot.Infra.Twitch/TwitchKnownBotService.cs
Original file line number Diff line number Diff line change
@@ -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<bool> 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}");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ public void ReturnFalse_AfterInvokingAction()
const int intervalInMinutes = 1;
var repository = new Mock<IRepository>();
repository.Setup(x => x.List(It.IsAny<ISpecification<ChatUser>>())).Returns(new List<ChatUser>());
var currencyGenerator = new CurrencyGenerator(new List<IChatClient>(), new ChatUserCollection(repository.Object));
var knownBotService = new Mock<IKnownBotService>();
var currencyGenerator = new CurrencyGenerator(new List<IChatClient>(),
new ChatUserCollection(repository.Object, knownBotService.Object));
var fakeClock = new FakeClock();
var currencyUpdate = new CurrencyUpdate(intervalInMinutes, currencyGenerator, fakeClock);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ public class AddCurrencyToShould
public void AllowGoingToMaxInt_GivenZeroStartAndMaxIntAdded()
{
var mockRepo = new Mock<IRepository>();
var currencyGenerator = new CurrencyGenerator(new List<IChatClient>(), new ChatUserCollection(mockRepo.Object));
var mockKnownBot = new Mock<IKnownBotService>();
var currencyGenerator = new CurrencyGenerator(new List<IChatClient>(),
new ChatUserCollection(mockRepo.Object, mockKnownBot.Object));

var chatUser = new ChatUser {Tokens = 0};
mockRepo.Setup(x => x.List(It.IsAny<ChatUserPolicy>())).Returns(new List<ChatUser> { chatUser });
Expand All @@ -32,7 +34,9 @@ public void AllowGoingToMaxInt_GivenZeroStartAndMaxIntAdded()
public void CapAtMaxInt_GivenOverflowPossibility()
{
var mockRepo = new Mock<IRepository>();
var currencyGenerator = new CurrencyGenerator(new List<IChatClient>(), new ChatUserCollection(mockRepo.Object));
var mockKnownBot = new Mock<IKnownBotService>();
var currencyGenerator = new CurrencyGenerator(new List<IChatClient>(),
new ChatUserCollection(mockRepo.Object, mockKnownBot.Object));

var chatUser = new ChatUser {Tokens = 100};
mockRepo.Setup(x => x.List(It.IsAny<ChatUserPolicy>())).Returns(new List<ChatUser> { chatUser });
Expand Down