Skip to content

Exclude known bots from earning coins while in chat #179

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

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
30 changes: 24 additions & 6 deletions src/DevChatter.Bot.Core/ChatUserCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> _activeChatUsers = new List<string>();

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);
}
Expand Down Expand Up @@ -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;
}
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: 2 additions & 3 deletions src/DevChatter.Bot.Core/Events/CurrencyGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
4 changes: 2 additions & 2 deletions src/DevChatter.Bot.Core/IChatUserCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to pass both displayName and chatUser, because the former is on the latter.

void StopWatching(string displayName);
bool TryGiveCoins(string coinGiver, string coinReceiver, int coinsToGive);
void UpdateEachChatter(Action<ChatUser> updateToApply);
void UpdateSpecificChatters(Action<ChatUser> updateToApply, ISpecification<ChatUser> filter);
bool UserHasAtLeast(string username, int tokensToRemove);
void WatchUser(string displayName);
void WatchUser(string displayName, ChatUser chatUser);
}
}
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
Loading